home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
PC World Komputer 2010 April
/
PCWorld0410.iso
/
pluginy Firefox
/
1843
/
1843.xpi
/
content
/
firebug
/
net.js
< prev
next >
Wrap
Text File
|
2010-01-15
|
165KB
|
5,209 lines
/* See license.txt for terms of usage */
FBL.ns(function() { with (FBL) {
// ************************************************************************************************
// Constants
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const CacheService = Cc["@mozilla.org/network/cache-service;1"];
const ImgCache = Cc["@mozilla.org/image/cache;1"];
const IOService = Cc["@mozilla.org/network/io-service;1"];
const prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch2);
const NOTIFY_ALL = Ci.nsIWebProgress.NOTIFY_ALL;
const nsIWebProgressListener = Ci.nsIWebProgressListener;
const STATE_IS_WINDOW = nsIWebProgressListener.STATE_IS_WINDOW;
const STATE_IS_DOCUMENT = nsIWebProgressListener.STATE_IS_DOCUMENT;
const STATE_IS_NETWORK = nsIWebProgressListener.STATE_IS_NETWORK;
const STATE_IS_REQUEST = nsIWebProgressListener.STATE_IS_REQUEST;
const STATE_START = nsIWebProgressListener.STATE_START;
const STATE_STOP = nsIWebProgressListener.STATE_STOP;
const STATE_TRANSFERRING = nsIWebProgressListener.STATE_TRANSFERRING;
const LOAD_BACKGROUND = Ci.nsIRequest.LOAD_BACKGROUND;
const LOAD_FROM_CACHE = Ci.nsIRequest.LOAD_FROM_CACHE;
const LOAD_DOCUMENT_URI = Ci.nsIChannel.LOAD_DOCUMENT_URI;
const NS_ERROR_CACHE_KEY_NOT_FOUND = 0x804B003D;
const NS_ERROR_CACHE_WAIT_FOR_VALIDATION = 0x804B0040;
var nsIHttpActivityObserver = Ci.nsIHttpActivityObserver;
var nsIHttpActivityObserver = Ci.nsIHttpActivityObserver;
var nsISocketTransport = Ci.nsISocketTransport;
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
const reIgnore = /about:|javascript:|resource:|chrome:|jar:/;
const layoutInterval = 300;
const indentWidth = 18;
var cacheSession = null;
var contexts = new Array();
var panelName = "net";
var maxQueueRequests = 500;
var panelBar1 = $("fbPanelBar1");
var activeRequests = [];
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
const mimeExtensionMap =
{
"txt": "text/plain",
"html": "text/html",
"htm": "text/html",
"xhtml": "text/html",
"xml": "text/xml",
"css": "text/css",
"js": "application/x-javascript",
"jss": "application/x-javascript",
"jpg": "image/jpg",
"jpeg": "image/jpeg",
"gif": "image/gif",
"png": "image/png",
"bmp": "image/bmp",
"swf": "application/x-shockwave-flash",
"flv": "video/x-flv"
};
const fileCategories =
{
"undefined": 1,
"html": 1,
"css": 1,
"js": 1,
"xhr": 1,
"image": 1,
"flash": 1,
"txt": 1,
"bin": 1
};
const textFileCategories =
{
"txt": 1,
"html": 1,
"xhr": 1,
"css": 1,
"js": 1
};
const binaryFileCategories =
{
"bin": 1,
"flash": 1
};
const mimeCategoryMap =
{
"text/plain": "txt",
"application/octet-stream": "bin",
"text/html": "html",
"text/xml": "html",
"text/css": "css",
"application/x-javascript": "js",
"text/javascript": "js",
"application/javascript" : "js",
"image/jpeg": "image",
"image/jpg": "image",
"image/gif": "image",
"image/png": "image",
"image/bmp": "image",
"application/x-shockwave-flash": "flash",
"video/x-flv": "flash"
};
const binaryCategoryMap =
{
"image": 1,
"flash" : 1
};
// ************************************************************************************************
/**
* @module Represents a module object for the Net panel. This object is derived
* from <code>Firebug.ActivableModule</code> in order to support activation (enable/disable).
* This allows to avoid (performance) expensive features if the functionality is not necessary
* for the user.
*/
Firebug.NetMonitor = extend(Firebug.ActivableModule,
{
dispatchName: "netMonitor",
clear: function(context)
{
// The user pressed a Clear button so, remove content of the panel...
var panel = context.getPanel(panelName, true);
if (panel)
panel.clear();
},
onToggleFilter: function(context, filterCategory)
{
if (!context.netProgress)
return;
Firebug.setPref(Firebug.prefDomain, "netFilterCategory", filterCategory);
// The content filter has been changed. Make sure that the content
// of the panel is updated (CSS is used to hide or show individual files).
var panel = context.getPanel(panelName, true);
if (panel)
{
panel.setFilter(filterCategory);
panel.updateSummaries(now(), true);
}
},
syncFilterButtons: function(chrome)
{
var button = chrome.$("fbNetFilter-" + Firebug.netFilterCategory);
button.checked = true;
},
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// extends Module
initializeUI: function()
{
Firebug.ActivableModule.initializeUI.apply(this, arguments);
// Initialize max limit for logged requests.
NetLimit.updateMaxLimit();
// Synchronize UI buttons with the current filter.
this.syncFilterButtons(FirebugChrome);
prefs.addObserver(Firebug.prefDomain, NetLimit, false);
},
initialize: function()
{
this.panelName = panelName;
Firebug.ActivableModule.initialize.apply(this, arguments);
if (Firebug.TraceModule)
Firebug.TraceModule.addListener(this.TraceListener);
// HTTP observer must be registered now (and not in monitorContext, since if a
// page is opened in a new tab the top document request would be missed otherwise.
NetHttpObserver.registerObserver();
NetHttpActivityObserver.registerObserver();
Firebug.Debugger.addListener(this.DebuggerListener);
},
internationalizeUI: function(doc)
{
var element = doc.getElementById("fbNetPersist");
FBL.internationalize(element, "label");
FBL.internationalize(element, "tooltiptext");
},
shutdown: function()
{
prefs.removeObserver(Firebug.prefDomain, this, false);
if (Firebug.TraceModule)
Firebug.TraceModule.removeListener(this.TraceListener);
NetHttpObserver.unregisterObserver();
NetHttpActivityObserver.unregisterObserver();
Firebug.Debugger.removeListener(this.DebuggerListener);
},
initContext: function(context, persistedState)
{
Firebug.ActivableModule.initContext.apply(this, arguments);
if (context.window && 'addEventListener' in context.window)
{
var window = context.window;
// Register "load" listener in order to track window load time.
var onWindowLoadHandler = function() {
if (context.netProgress)
context.netProgress.post(windowLoad, [window, now()]);
window.removeEventListener("load", onWindowLoadHandler, true);
}
window.addEventListener("load", onWindowLoadHandler, true);
// Register "DOMContentLoaded" listener to track timing.
var onContentLoadHandler = function() {
if (context.netProgress)
context.netProgress.post(contentLoad, [window, now()]);
window.removeEventListener("DOMContentLoaded", onContentLoadHandler, true);
}
window.addEventListener("DOMContentLoaded", onContentLoadHandler, true);
}
if (Firebug.NetMonitor.isAlwaysEnabled())
monitorContext(context);
if (context.netProgress)
{
// Load existing breakpoints
var persistedPanelState = getPersistedState(context, panelName);
if (persistedPanelState.breakpoints)
context.netProgress.breakpoints = persistedPanelState.breakpoints;
}
},
reattachContext: function(browser, context)
{
Firebug.ActivableModule.reattachContext.apply(this, arguments);
this.syncFilterButtons(Firebug.chrome);
},
destroyContext: function(context, persistedState)
{
Firebug.ActivableModule.destroyContext.apply(this, arguments);
if (context.netProgress)
{
// Remember existing breakpoints.
var persistedPanelState = getPersistedState(context, panelName);
persistedPanelState.breakpoints = context.netProgress.breakpoints;
}
if (Firebug.NetMonitor.isAlwaysEnabled())
unmonitorContext(context);
},
showContext: function(browser, context)
{
Firebug.ActivableModule.showContext.apply(this, arguments);
},
loadedContext: function(context)
{
var tabId = Firebug.getTabIdForWindow(context.browser.contentWindow);
delete contexts[tabId];
var netProgress = context.netProgress;
if (netProgress)
{
netProgress.loaded = true;
// Set Page title and id into all document objects.
for (var i=0; i<netProgress.documents.length; i++)
{
var doc = netProgress.documents[i];
doc.id = context.uid;
doc.title = context.getTitle();
}
}
},
onEnabled: function(context)
{
NetHttpActivityObserver.registerObserver();
monitorContext(context);
},
onDisabled: function(context)
{
NetHttpActivityObserver.unregisterObserver();
unmonitorContext(context);
},
onResumeFirebug: function()
{
if (Firebug.NetMonitor.isAlwaysEnabled())
TabWatcher.iterateContexts(monitorContext);
},
onSuspendFirebug: function()
{
if (Firebug.NetMonitor.isAlwaysEnabled())
TabWatcher.iterateContexts(unmonitorContext);
},
togglePersist: function(context)
{
var panel = context.getPanel(panelName);
panel.persistContent = panel.persistContent ? false : true;
Firebug.chrome.setGlobalAttribute("cmd_togglePersistNet", "checked", panel.persistContent);
}
});
// ************************************************************************************************
/**
* @panel Represents a Firebug panel that displayes info about HTTP activity associated with
* the current page. This class is derived from <code>Firebug.ActivablePanel</code> in order
* to support activation (enable/disable). This allows to avoid (performance) expensive
* features if the functionality is not necessary for the user.
*/
function NetPanel() {}
NetPanel.prototype = extend(Firebug.ActivablePanel,
{
name: panelName,
searchable: true,
editable: true,
breakable: true,
initialize: function(context, doc)
{
this.queue = [];
Firebug.registerUIListener(this);
this.onContextMenu = bind(this.onContextMenu, this);
Firebug.ActivablePanel.initialize.apply(this, arguments);
},
destroy: function(state)
{
Firebug.ActivablePanel.destroy.apply(this, arguments);
Firebug.unregisterUIListener(this);
},
initializeNode : function()
{
this.panelNode.addEventListener("contextmenu", this.onContextMenu, false);
dispatch([Firebug.A11yModel], "onInitializeNode", [this]);
},
destroyNode : function()
{
this.panelNode.removeEventListener("contextmenu", this.onContextMenu, false);
dispatch([Firebug.A11yModel], "onDestroyNode", [this]);
},
loadPersistedContent: function(state)
{
this.initLayout();
var tbody = this.table.firstChild;
var lastRow = tbody.lastChild.previousSibling;
// Move all net-rows from the persistedState to this panel.
var prevTableBody = state.panelNode.getElementsByClassName("netTableBody").item(0);
if (!prevTableBody)
return;
var files = [];
while (prevTableBody.firstChild)
{
var row = prevTableBody.firstChild;
if (hasClass(row, "netRow") && hasClass(row, "hasHeaders") && !hasClass(row, "history"))
{
row.repObject.history = true;
files.push({
file: row.repObject,
offset: 0 + "%",
width: 0 + "%",
elapsed: -1
});
}
if (hasClass(row, "netPageRow"))
{
removeClass(row, "opened");
tbody.insertBefore(row, lastRow);
}
else
prevTableBody.removeChild(row);
}
if (files.length)
{
var pageRow = NetPage.pageTag.insertRows({page: state}, lastRow)[0];
pageRow.files = files;
lastRow = tbody.lastChild.previousSibling;
}
if (this.table.getElementsByClassName("netPageRow").item(0))
NetPage.separatorTag.insertRows({}, lastRow);
scrollToBottom(this.panelNode);
},
savePersistedContent: function(state)
{
Firebug.ActivablePanel.savePersistedContent.apply(this, arguments);
state.pageTitle = this.context.getTitle();
},
// UI Listener
showUI: function(browser, context)
{
},
hideUI: function(browser, context)
{
},
show: function(state)
{
var enabled = Firebug.NetMonitor.isAlwaysEnabled();
this.showToolbarButtons("fbNetButtons", enabled);
if (enabled)
{
Firebug.NetMonitor.disabledPanelPage.hide(this);
Firebug.chrome.setGlobalAttribute("cmd_togglePersistNet", "checked", this.persistContent);
}
else
{
Firebug.NetMonitor.disabledPanelPage.show(this);
this.table = null;
}
if (!enabled)
return;
if (!this.filterCategory)
this.setFilter(Firebug.netFilterCategory);
this.layout();
if (!this.layoutInterval)
this.layoutInterval = setInterval(bindFixed(this.updateLayout, this), layoutInterval);
if (this.wasScrolledToBottom)
scrollToBottom(this.panelNode);
},
hide: function()
{
this.showToolbarButtons("fbNetButtons", false);
Firebug.Debugger.syncCommands(this.context);
delete this.infoTipURL; // clear the state that is tracking the infotip so it is reset after next show()
this.wasScrolledToBottom = isScrolledToBottom(this.panelNode);
clearInterval(this.layoutInterval);
delete this.layoutInterval;
},
updateOption: function(name, value)
{
if (name == "netFilterCategory")
{
Firebug.NetMonitor.syncFilterButtons(Firebug.chrome);
for (var i = 0; i < TabWatcher.contexts.length; ++i)
{
var context = TabWatcher.contexts[i];
Firebug.NetMonitor.onToggleFilter(context, value);
}
}
},
updateSelection: function(object)
{
if (!object)
return;
var netProgress = this.context.netProgress;
var file = netProgress.getRequestFile(object.request);
if (!file)
{
for (var i=0; i<netProgress.requests.length; i++) {
if (safeGetName(netProgress.requests[i]) == object.href) {
file = netProgress.files[i];
break;
}
}
}
if (file)
{
scrollIntoCenterView(file.row);
if (!hasClass(file.row, "opened"))
NetRequestEntry.toggleHeadersRow(file.row);
}
},
getPopupObject: function(target)
{
var header = getAncestorByClass(target, "netHeaderRow");
if (header)
return NetRequestTable;
return Firebug.ActivablePanel.getPopupObject.apply(this, arguments);
},
supportsObject: function(object)
{
return ((object instanceof SourceLink && object.type == "net") ? 2 : 0);
},
getOptionsMenuItems: function()
{
return [
this.disableCacheOption()
];
},
disableCacheOption: function()
{
var BrowserCache = Firebug.NetMonitor.BrowserCache;
var disabled = !BrowserCache.isEnabled();
return {label: "net.option.Disable Browser Cache", type: "checkbox", checked: disabled,
command: bindFixed(BrowserCache.enable, BrowserCache, disabled) };
},
getContextMenuItems: function(nada, target)
{
var items = [];
var file = Firebug.getRepObject(target);
if (!file || !(file instanceof NetFile))
return items;
var object = Firebug.getObjectByURL(this.context, file.href);
var isPost = Utils.isURLEncodedRequest(file, this.context);
items.push(
{label: "CopyLocation", command: bindFixed(copyToClipboard, FBL, file.href) }
);
if (isPost)
{
items.push(
{label: "CopyLocationParameters", command: bindFixed(this.copyParams, this, file) }
);
}
items.push(
{label: "CopyRequestHeaders",
command: bindFixed(this.copyHeaders, this, file.requestHeaders) },
{label: "CopyResponseHeaders",
command: bindFixed(this.copyHeaders, this, file.responseHeaders) }
);
if (textFileCategories.hasOwnProperty(file.category))
{
items.push(
{label: "CopyResponse", command: bindFixed(this.copyResponse, this, file) }
);
}
items.push(
"-",
{label: "OpenInTab", command: bindFixed(this.openRequestInTab, this, file) }
);
if (textFileCategories.hasOwnProperty(file.category))
{
items.push(
{label: "Open Response In New Tab", command: bindFixed(this.openResponseInTab, this, file) }
);
}
if (!file.loaded)
{
items.push(
"-",
{label: "StopLoading", command: bindFixed(this.stopLoading, this, file) }
);
}
if (object)
{
var subItems = Firebug.chrome.getInspectMenuItems(object);
if (subItems.length)
{
items.push("-");
items.push.apply(items, subItems);
}
}
if (file.isXHR)
{
var bp = this.context.netProgress.breakpoints.findBreakpoint(file.getFileURL());
items.push(
"-",
{label: "net.label.Break On XHR", type: "checkbox", checked: !!bp,
command: bindFixed(this.breakOnRequest, this, file) }
);
if (bp)
{
items.push(
{label: "EditBreakpointCondition",
command: bindFixed(this.editBreakpointCondition, this, file) }
);
}
}
return items;
},
// Context menu commands
copyParams: function(file)
{
var text = Utils.getPostText(file, this.context, true);
var url = reEncodeURL(file, text, true);
copyToClipboard(url);
},
copyHeaders: function(headers)
{
var lines = [];
if (headers)
{
for (var i = 0; i < headers.length; ++i)
{
var header = headers[i];
lines.push(header.name + ": " + header.value);
}
}
var text = lines.join("\r\n");
copyToClipboard(text);
},
copyResponse: function(file)
{
var allowDoublePost = Firebug.getPref(Firebug.prefDomain, "allowDoublePost");
if (!allowDoublePost && !file.cacheEntry)
{
if (!confirm("The response can be re-requested from the server, OK?"))
return;
}
// Copy response to the clipboard
copyToClipboard(Utils.getResponseText(file, this.context));
},
openRequestInTab: function(file)
{
openNewTab(file.href, file.postText);
},
openResponseInTab: function(file)
{
try
{
var response = Utils.getResponseText(file, this.context);
var inputStream = getInputStreamFromString(response);
var stream = CCIN("@mozilla.org/binaryinputstream;1", "nsIBinaryInputStream");
stream.setInputStream(inputStream);
var encodedResponse = btoa(stream.readBytes(stream.available()));
var dataURI = "data:" + file.request.contentType + ";base64," + encodedResponse;
gBrowser.selectedTab = gBrowser.addTab(dataURI);
}
catch (err)
{
}
},
breakOnRequest: function(file)
{
if (!file.isXHR)
return;
// Create new or remove an existing breakpoint.
var breakpoints = this.context.netProgress.breakpoints;
var url = file.getFileURL();
var bp = breakpoints.findBreakpoint(url);
if (bp)
breakpoints.removeBreakpoint(url);
else
breakpoints.addBreakpoint(url);
this.enumerateRequests(function(currFile)
{
if (url != currFile.getFileURL())
return;
if (bp)
currFile.row.removeAttribute("breakpoint");
else
currFile.row.setAttribute("breakpoint", "true");
})
},
stopLoading: function(file)
{
const NS_BINDING_ABORTED = 0x804b0002;
file.request.cancel(NS_BINDING_ABORTED);
},
// Support for xhr breakpoint conditions.
onContextMenu: function(event)
{
if (!hasClass(event.target, "sourceLine"))
return;
var row = getAncestorByClass(event.target, "netRow");
if (!row)
return;
var file = row.repObject;
var bp = this.context.netProgress.breakpoints.findBreakpoint(file.getFileURL());
if (!bp)
return;
this.editBreakpointCondition(file);
cancelEvent(event);
},
editBreakpointCondition: function(file)
{
var bp = this.context.netProgress.breakpoints.findBreakpoint(file.getFileURL());
if (!bp)
return;
var condition = bp ? bp.condition : "";
this.selectedSourceBox = this.panelNode;
Firebug.Editor.startEditing(file.row, condition);
},
getEditor: function(target, value)
{
if (!this.conditionEditor)
this.conditionEditor = new Firebug.NetMonitor.ConditionEditor(this.document);
return this.conditionEditor;
},
// Support for activation.
disablePanel: function(module)
{
Firebug.ActivablePanel.disablePanel.apply(this, arguments);
this.table = null;
},
breakOnNext: function(breaking)
{
this.context.breakOnXHR = breaking;
},
shouldBreakOnNext: function()
{
return this.context.breakOnXHR;
},
getBreakOnNextTooltip: function(enabled)
{
return (enabled ? $STR("net.Disable Break On XHR") : $STR("net.Break On XHR"));
},
// Support for info tips.
showInfoTip: function(infoTip, target, x, y)
{
var row = getAncestorByClass(target, "netRow");
if (row && row.repObject)
{
if (getAncestorByClass(target, "netTotalSizeCol"))
{
var infoTipURL = "netTotalSize";
if (infoTipURL == this.infoTipURL)
return true;
this.infoTipURL = infoTipURL;
return this.populateTotalSizeInfoTip(infoTip, row);
}
else if (getAncestorByClass(target, "netSizeCol"))
{
var infoTipURL = row.repObject.href + "-netsize";
if (infoTipURL == this.infoTipURL && row.repObject == this.infoTipFile)
return true;
this.infoTipURL = infoTipURL;
this.infoTipFile = row.repObject;
return this.populateSizeInfoTip(infoTip, row.repObject);
}
else if (getAncestorByClass(target, "netTimeCol"))
{
var infoTipURL = row.repObject.href + "-nettime";
if (infoTipURL == this.infoTipURL && row.repObject == this.infoTipFile)
return true;
this.infoTipURL = infoTipURL;
this.infoTipFile = row.repObject;
return this.populateTimeInfoTip(infoTip, row.repObject);
}
else if (hasClass(row, "category-image") &&
!getAncestorByClass(target, "netRowHeader"))
{
var infoTipURL = row.repObject.href + "-image";
if (infoTipURL == this.infoTipURL)
return true;
this.infoTipURL = infoTipURL;
return Firebug.InfoTip.populateImageInfoTip(infoTip, row.repObject.href);
}
}
},
populateTimeInfoTip: function(infoTip, file)
{
Firebug.NetMonitor.TimeInfoTip.render(file, infoTip);
return true;
},
populateSizeInfoTip: function(infoTip, file)
{
Firebug.NetMonitor.SizeInfoTip.render(file, infoTip);
return true;
},
populateTotalSizeInfoTip: function(infoTip, row)
{
var totalSizeLabel = row.getElementsByClassName("netTotalSizeLabel").item(0);
var file = {size: totalSizeLabel.getAttribute("totalSize")};
Firebug.NetMonitor.SizeInfoTip.tag.replace({file: file}, infoTip);
return true;
},
// Support for search within the panel.
getSearchOptionsMenuItems: function()
{
return [
Firebug.Search.searchOptionMenu("search.Case Sensitive", "searchCaseSensitive"),
//Firebug.Search.searchOptionMenu("search.net.Headers", "netSearchHeaders"),
//Firebug.Search.searchOptionMenu("search.net.Parameters", "netSearchParameters"),
Firebug.Search.searchOptionMenu("search.net.Response Bodies", "netSearchResponseBody")
];
},
search: function(text, reverse)
{
if (!text)
{
delete this.currentSearch;
return false;
}
var row;
if (this.currentSearch && text == this.currentSearch.text)
{
row = this.currentSearch.findNext(true, false, reverse, Firebug.Search.isCaseSensitive(text));
}
else
{
this.currentSearch = new NetPanelSearch(this);
row = this.currentSearch.find(text, reverse, Firebug.Search.isCaseSensitive(text));
}
if (row)
{
var sel = this.document.defaultView.getSelection();
sel.removeAllRanges();
sel.addRange(this.currentSearch.range);
scrollIntoCenterView(row, this.panelNode);
dispatch([Firebug.A11yModel], 'onNetMatchFound', [this, text, row]);
return true;
}
else
{
dispatch([Firebug.A11yModel], 'onNetMatchFound', [this, text, null]);
return false;
}
},
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
updateFile: function(file)
{
if (!file.invalid)
{
file.invalid = true;
this.queue.push(file);
}
},
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
updateLayout: function()
{
if (!this.queue.length)
return;
var rightNow = now();
var length = this.queue.length;
if (this.panelNode.offsetHeight)
this.wasScrolledToBottom = isScrolledToBottom(this.panelNode);
this.layout();
if (this.wasScrolledToBottom)
scrollToBottom(this.panelNode);
},
layout: function()
{
if (!this.queue.length || !this.context.netProgress ||
!Firebug.NetMonitor.isAlwaysEnabled())
return;
this.initLayout();
var rightNow = now();
this.updateRowData(rightNow);
this.updateLogLimit(maxQueueRequests);
this.updateTimeline(rightNow);
this.updateSummaries(rightNow);
},
initLayout: function()
{
if (!this.table)
{
var limitInfo = {
totalCount: 0,
limitPrefsTitle: $STRF("LimitPrefsTitle", [Firebug.prefDomain+".net.logLimit"])
};
this.table = NetRequestTable.tableTag.append({}, this.panelNode);
this.limitRow = NetLimit.createRow(this.table.firstChild, limitInfo);
this.summaryRow = NetRequestEntry.summaryTag.insertRows({}, this.table.lastChild.lastChild)[0];
// Update visibility of columns according to the preferences
var hiddenCols = Firebug.getPref(Firebug.prefDomain, "net.hiddenColumns");
if (hiddenCols)
this.table.setAttribute("hiddenCols", hiddenCols);
}
},
updateRowData: function(rightNow)
{
var queue = this.queue;
this.queue = [];
var phase;
var newFileData = [];
for (var i = 0; i < queue.length; ++i)
{
var file = queue[i];
if (!file.phase)
continue;
file.invalid = false;
phase = this.calculateFileTimes(file, phase, rightNow);
this.updateFileRow(file, newFileData);
this.invalidatePhase(phase);
}
if (newFileData.length)
{
var tbody = this.table.firstChild;
var lastRow = tbody.lastChild.previousSibling;
this.insertRows(newFileData, lastRow);
}
},
insertRows: function(files, lastRow)
{
var row = NetRequestEntry.fileTag.insertRows({files: files}, lastRow)[0];
for (var i = 0; i < files.length; ++i)
{
var file = files[i].file;
row.repObject = file;
file.row = row;
// Make sure a breakpoint is displayed.
var breakpoints = this.context.netProgress.breakpoints;
if (breakpoints && breakpoints.findBreakpoint(file.getFileURL()))
row.setAttribute("breakpoint", "true");
// Allow customization of request entries in the list. A row is represented
// by <TR> HTML element.
dispatch(NetRequestTable.fbListeners, "onCreateRequestEntry", [this, row]);
row = row.nextSibling;
}
},
invalidatePhase: function(phase)
{
if (phase && !phase.invalidPhase)
{
phase.invalidPhase = true;
this.invalidPhases = true;
}
},
updateFileRow: function(file, newFileData)
{
var row = file.row;
if (!row)
{
newFileData.push({
file: file,
offset: this.barOffset + "%",
width: this.barReceivingWidth + "%",
elapsed: file.loaded ? this.elapsed : -1
});
}
else
{
var sizeLabel = row.childNodes[4].firstChild;
var sizeText = NetRequestEntry.getSize(file);
// Show also total downloaded size for requests in progress.
if (file.totalReceived)
sizeText += " (" + formatSize(file.totalReceived) + ")";
sizeLabel.firstChild.nodeValue = sizeText;
var methodLabel = row.childNodes[2].firstChild;
methodLabel.firstChild.nodeValue = NetRequestEntry.getStatus(file);
var hrefLabel = row.childNodes[1].firstChild;
hrefLabel.firstChild.nodeValue = NetRequestEntry.getHref(file);
if (file.mimeType)
{
// Force update category.
file.category = null;
for (var category in fileCategories)
removeClass(row, "category-" + category);
setClass(row, "category-" + Utils.getFileCategory(file));
}
if (file.responseHeaders)
setClass(row, "hasHeaders");
if (file.fromCache)
setClass(row, "fromCache");
else
removeClass(row, "fromCache");
if (NetRequestEntry.isError(file))
setClass(row, "responseError");
else
removeClass(row, "responseError");
var timeLabel = row.childNodes[5].childNodes[1].lastChild.firstChild;
timeLabel.innerHTML = NetRequestEntry.getElapsedTime({elapsed: this.elapsed});
if (file.loaded)
setClass(row, "loaded");
else
removeClass(row, "loaded");
if (hasClass(row, "opened"))
{
var netInfoBox = row.nextSibling.getElementsByClassName("netInfoBody").item(0);
NetInfoBody.updateInfo(netInfoBox, file, this.context);
}
}
},
updateTimeline: function(rightNow)
{
var tbody = this.table.firstChild;
// XXXjoe Don't update rows whose phase is done and layed out already
var phase;
for (var row = tbody.firstChild; row; row = row.nextSibling)
{
var file = row.repObject;
// Some rows aren't associated with a file (e.g. header, sumarry).
if (!file)
continue;
if (!file.loaded)
continue;
phase = this.calculateFileTimes(file, phase, rightNow);
// Get bar nodes
var blockingBar = row.childNodes[5].childNodes[1].childNodes[1];
var resolvingBar = blockingBar.nextSibling;
var connectingBar = resolvingBar.nextSibling;
var sendingBar = connectingBar.nextSibling;
var waitingBar = sendingBar.nextSibling;
var contentLoadBar = waitingBar.nextSibling;
var windowLoadBar = contentLoadBar.nextSibling;
var receivingBar = windowLoadBar.nextSibling;
// All bars starts at the beginning
resolvingBar.style.left = connectingBar.style.left = sendingBar.style.left =
blockingBar.style.left =
waitingBar.style.left = receivingBar.style.left = this.barOffset + "%";
// Sets width of all bars (using style). The width is computed according to measured timing.
blockingBar.style.width = this.barBlockingWidth + "%";
resolvingBar.style.width = this.barResolvingWidth + "%";
connectingBar.style.width = this.barConnectingWidth + "%";
sendingBar.style.width = this.barSendingWidth + "%";
waitingBar.style.width = this.barWaitingWidth + "%";
receivingBar.style.width = this.barReceivingWidth + "%";
if (this.contentLoadBarOffset) {
contentLoadBar.style.left = this.contentLoadBarOffset + "%";
contentLoadBar.style.display = "block";
this.contentLoadBarOffset = null;
}
if (this.windowLoadBarOffset) {
windowLoadBar.style.left = this.windowLoadBarOffset + "%";
windowLoadBar.style.display = "block";
this.windowLoadBarOffset = null;
}
}
},
calculateFileTimes: function(file, phase, rightNow)
{
var phases = this.context.netProgress.phases;
if (phase != file.phase)
{
phase = file.phase;
this.phaseStartTime = phase.startTime;
this.phaseEndTime = phase.endTime ? phase.endTime : rightNow;
// End of the first phase has to respect even the window "onload" event time, which
// can occur after the last received file. This sets the extent of the timeline so,
// the windowLoadBar is visible.
if (phase.windowLoadTime && this.phaseEndTime < phase.windowLoadTime)
this.phaseEndTime = phase.windowLoadTime;
this.phaseElapsed = this.phaseEndTime - phase.startTime;
}
var elapsed = file.loaded ? file.endTime - file.startTime : 0; /*this.phaseEndTime - file.startTime*/
this.barOffset = Math.floor(((file.startTime-this.phaseStartTime)/this.phaseElapsed) * 100);
var blockingEnd = (file.sendingTime != file.startTime) ? file.sendingTime : file.waitingForTime;
this.barResolvingWidth = Math.round(((file.connectingTime - file.startTime) / this.phaseElapsed) * 100);
this.barConnectingWidth = Math.round(((file.connectedTime - file.startTime) / this.phaseElapsed) * 100);
this.barBlockingWidth = Math.round(((blockingEnd - file.startTime) / this.phaseElapsed) * 100);
this.barSendingWidth = Math.round(((file.waitingForTime - file.startTime) / this.phaseElapsed) * 100);
this.barWaitingWidth = Math.round(((file.respondedTime - file.startTime) / this.phaseElapsed) * 100);
this.barReceivingWidth = Math.round((elapsed / this.phaseElapsed) * 100);
// Total request time doesn't include the time spent in queue.
// xxxHonza: since all phases are now graphically distinguished it's easy to
// see blocking requests. It's make sense to display the real total time now.
this.elapsed = elapsed/* - (file.sendingTime - file.connectedTime)*/;
// The nspr timer doesn't have 1ms precision, so it can happen that entire
// request is executed in l ms (so the total is zero). Let's display at least
// one bar in such a case so the timeline is visible.
if (this.elapsed <= 0)
this.barReceivingWidth = "1";
// Compute also offset for the contentLoadBar and windowLoadBar, which are
// displayed for the first phase.
if (phase.contentLoadTime)
this.contentLoadBarOffset = Math.floor(((phase.contentLoadTime-this.phaseStartTime)/this.phaseElapsed) * 100);
if (phase.windowLoadTime)
this.windowLoadBarOffset = Math.floor(((phase.windowLoadTime-this.phaseStartTime)/this.phaseElapsed) * 100);
return phase;
},
updateSummaries: function(rightNow, updateAll)
{
if (!this.invalidPhases && !updateAll)
return;
this.invalidPhases = false;
var phases = this.context.netProgress.phases;
if (!phases.length)
return;
var fileCount = 0, totalSize = 0, cachedSize = 0, totalTime = 0;
for (var i = 0; i < phases.length; ++i)
{
var phase = phases[i];
phase.invalidPhase = false;
var summary = this.summarizePhase(phase, rightNow);
fileCount += summary.fileCount;
totalSize += summary.totalSize;
cachedSize += summary.cachedSize;
totalTime += summary.totalTime
}
var row = this.summaryRow;
if (!row)
return;
var countLabel = row.childNodes[1].firstChild;
countLabel.firstChild.nodeValue = $STRP("plural.Request_Count", [fileCount]);
var sizeLabel = row.childNodes[4].firstChild;
sizeLabel.setAttribute("totalSize", totalSize);
sizeLabel.firstChild.nodeValue = NetRequestEntry.formatSize(totalSize);
var cacheSizeLabel = row.lastChild.firstChild.firstChild;
cacheSizeLabel.setAttribute("collapsed", cachedSize == 0);
cacheSizeLabel.childNodes[1].firstChild.nodeValue =
NetRequestEntry.formatSize(cachedSize);
var timeLabel = row.lastChild.firstChild.lastChild.firstChild;
var timeText = NetRequestEntry.formatTime(totalTime);
var firstPhase = phases[0];
if (firstPhase.windowLoadTime)
{
var loadTime = firstPhase.windowLoadTime - firstPhase.startTime;
// xxxHonza: localization?
timeText += " (onload: " + NetRequestEntry.formatTime(loadTime) + ")";
}
timeLabel.innerHTML = timeText;
},
summarizePhase: function(phase, rightNow)
{
var cachedSize = 0, totalSize = 0;
var category = Firebug.netFilterCategory;
if (category == "all")
category = null;
var fileCount = 0;
var minTime = 0, maxTime = 0;
for (var i=0; i<phase.files.length; i++)
{
var file = phase.files[i];
if (!category || file.category == category)
{
if (file.loaded)
{
++fileCount;
if (file.size > 0)
{
totalSize += file.size;
if (file.fromCache)
cachedSize += file.size;
}
if (!minTime || file.startTime < minTime)
minTime = file.startTime;
if (file.endTime > maxTime)
maxTime = file.endTime;
}
}
}
var totalTime = maxTime - minTime;
return {cachedSize: cachedSize, totalSize: totalSize, totalTime: totalTime,
fileCount: fileCount}
},
updateLogLimit: function(limit)
{
var netProgress = this.context.netProgress;
if (!netProgress) // XXXjjb Honza, please check, I guess we are getting here with the context not setup
{
return;
}
// Must be positive number;
limit = Math.max(0, limit);
var filesLength = netProgress.files.length;
if (!filesLength || filesLength <= limit)
return;
// Remove old requests.
var removeCount = Math.max(0, filesLength - limit);
for (var i=0; i<removeCount; i++)
{
var file = netProgress.files[0];
this.removeLogEntry(file);
// Remove the file occurrence from the queue.
for (var j=0; j<this.queue.length; j++)
{
if (this.queue[j] == file) {
this.queue.splice(j, 1);
j--;
}
}
}
},
removeLogEntry: function(file, noInfo)
{
if (!this.removeFile(file))
return;
if (!this.table || !this.table.firstChild)
return;
if (file.row)
{
// The file is loaded and there is a row that has to be removed from the UI.
var tbody = this.table.firstChild;
clearDomplate(file.row);
tbody.removeChild(file.row);
}
if (noInfo || !this.limitRow)
return;
this.limitRow.limitInfo.totalCount++;
NetLimit.updateCounter(this.limitRow);
//if (netProgress.currentPhase == file.phase)
// netProgress.currentPhase = null;
},
removeFile: function(file)
{
var netProgress = this.context.netProgress;
var index = netProgress.files.indexOf(file);
if (index == -1)
return false;
netProgress.files.splice(index, 1);
netProgress.requests.splice(index, 1);
// Don't forget to remove the phase whose last file has been removed.
var phase = file.phase;
// xxxHonza: This needs to be examined yet. Looks like the queue contains
// requests from the previous page. When flushed the requestedFile isn't called
// and the phase is not set.
if (!phase)
return true;
phase.removeFile(file);
if (!phase.files.length)
{
remove(netProgress.phases, phase);
if (netProgress.currentPhase == phase)
netProgress.currentPhase = null;
}
return true;
},
insertActivationMessage: function()
{
if (!Firebug.NetMonitor.isAlwaysEnabled())
return;
// Make sure the basic structure of the table panel is there.
this.initLayout();
// Get the last request row before summary row.
var tbody = this.table.firstChild;
var lastRow = tbody.lastChild.previousSibling;
// Insert an activation message (if the last row isn't the message already);
if (hasClass(lastRow, "netActivationRow"))
return;
var message = NetRequestEntry.activationTag.insertRows({}, lastRow)[0];
},
enumerateRequests: function(fn)
{
if (!this.table)
return;
var rows = this.table.getElementsByClassName("netRow");
for (var i=0; i<rows.length; i++)
{
var row = rows[i];
var pageRow = hasClass(row, "netPageRow");
if (hasClass(row, "collapsed") && !pageRow)
continue;
if (hasClass(row, "history"))
continue;
// Export also history. These requests can be collpased and so not visible.
if (row.files)
{
for (var j=0; j<row.files.length; j++)
fn(row.files[j].file);
}
var file = Firebug.getRepObject(row);
if (file)
fn(file);
}
},
setFilter: function(filterCategory)
{
this.filterCategory = filterCategory;
var panelNode = this.panelNode;
for (var category in fileCategories)
{
if (filterCategory != "all" && category != filterCategory)
setClass(panelNode, "hideCategory-"+category);
else
removeClass(panelNode, "hideCategory-"+category);
}
},
clear: function()
{
clearNode(this.panelNode);
this.table = null;
this.summaryRow = null;
this.limitRow = null;
this.queue = [];
this.invalidPhases = false;
if (this.context.netProgress)
this.context.netProgress.clear();
},
});
// ************************************************************************************************
/**
* @domplate Represents a template that is used to render basic content of the net panel.
*/
Firebug.NetMonitor.NetRequestTable = domplate(Firebug.Rep, new Firebug.Listener(),
{
inspectable: false,
tableTag:
TABLE({"class": "netTable", cellpadding: 0, cellspacing: 0, hiddenCols: "", "role": "treegrid"},
TBODY({"class": "netTableBody", "role" : "presentation"},
TR({"class": "netHeaderRow netRow focusRow outerFocusRow", onclick: "$onClickHeader", "role": "row"},
TD({id: "netBreakpointBar", width: "1%", "class": "netHeaderCell",
"role": "columnheader"},
" "
),
TD({id: "netHrefCol", width: "18%", "class": "netHeaderCell alphaValue a11yFocus",
"role": "columnheader"},
DIV({"class": "netHeaderCellBox",
title: $STR("net.header.URL Tooltip")},
$STR("net.header.URL"))
),
TD({id: "netStatusCol", width: "12%", "class": "netHeaderCell alphaValue a11yFocus",
"role": "columnheader"},
DIV({"class": "netHeaderCellBox",
title: $STR("net.header.Status Tooltip")},
$STR("net.header.Status"))
),
TD({id: "netDomainCol", width: "12%", "class": "netHeaderCell alphaValue a11yFocus",
"role": "columnheader"},
DIV({"class": "netHeaderCellBox",
title: $STR("net.header.Domain Tooltip")},
$STR("net.header.Domain"))
),
TD({id: "netSizeCol", width: "4%", "class": "netHeaderCell a11yFocus",
"role": "columnheader"},
DIV({"class": "netHeaderCellBox",
title: $STR("net.header.Size Tooltip")},
$STR("net.header.Size"))
),
TD({id: "netTimeCol", width: "53%", "class": "netHeaderCell a11yFocus",
"role": "columnheader"},
DIV({"class": "netHeaderCellBox",
title: $STR("net.header.Timeline Tooltip")},
$STR("net.header.Timeline"))
)
)
)
),
onClickHeader: function(event)
{
if (!isLeftClick(event) && !(event.type == "keypress" && event.keyCode == 13))
return;
var table = getAncestorByClass(event.target, "netTable");
var column = getAncestorByClass(event.target, "netHeaderCell");
this.sortColumn(table, column);
},
sortColumn: function(table, col, direction)
{
if (!col)
return;
var numerical = !hasClass(col, "alphaValue");
var colIndex = 0;
for (col = col.previousSibling; col; col = col.previousSibling)
++colIndex;
// the first breakpoint bar column is not sortable.
if (colIndex == 0)
return;
this.sort(table, colIndex, numerical, direction);
},
sort: function(table, colIndex, numerical, direction)
{
var tbody = table.lastChild;
var headerRow = tbody.firstChild;
// Remove class from the currently sorted column
var headerSorted = getChildByClass(headerRow, "netHeaderSorted");
removeClass(headerSorted, "netHeaderSorted");
if (headerSorted)
headerSorted.removeAttribute("aria-sort");
// Mark new column as sorted.
var header = headerRow.childNodes[colIndex];
setClass(header, "netHeaderSorted");
// If the column is already using required sort direction, bubble out.
if ((direction == "desc" && header.sorted == 1) ||
(direction == "asc" && header.sorted == -1))
return;
if (header)
header.setAttribute("aria-sort", header.sorted === -1 ? "descending" : "ascending");
var colID = header.getAttribute("id");
var values = [];
for (var row = tbody.childNodes[1]; row; row = row.nextSibling)
{
if (!row.repObject)
continue;
if (hasClass(row, "history"))
continue;
var cell = row.childNodes[colIndex];
var value = numerical ? parseFloat(cell.textContent) : cell.textContent;
if (colID == "netTimeCol")
value = row.repObject.startTime;
else if (colID == "netSizeCol")
value = row.repObject.size;
if (hasClass(row, "opened"))
{
var netInfoRow = row.nextSibling;
values.push({row: row, value: value, info: netInfoRow});
row = netInfoRow;
}
else
{
values.push({row: row, value: value});
}
}
values.sort(function(a, b) { return a.value < b.value ? -1 : 1; });
if ((header.sorted && header.sorted == 1) || (!header.sorted && direction == "asc"))
{
removeClass(header, "sortedDescending");
setClass(header, "sortedAscending");
header.sorted = -1;
for (var i = 0; i < values.length; ++i)
{
tbody.appendChild(values[i].row);
if (values[i].info)
tbody.appendChild(values[i].info);
}
}
else
{
removeClass(header, "sortedAscending");
setClass(header, "sortedDescending");
header.sorted = 1;
for (var i = values.length-1; i >= 0; --i)
{
tbody.appendChild(values[i].row);
if (values[i].info)
tbody.appendChild(values[i].info);
}
}
// Make sure the summary row is again at the end.
var summaryRow = tbody.getElementsByClassName("netSummaryRow").item(0);
tbody.appendChild(summaryRow);
},
supportsObject: function(object)
{
return (object == this);
},
/**
* Provides menu items for header context menu.
*/
getContextMenuItems: function(object, target, context)
{
var popup = $("fbContextMenu");
if (popup.firstChild && popup.firstChild.getAttribute("command") == "cmd_copy")
popup.removeChild(popup.firstChild);
var items = [];
// Iterate over all columns and create a menu item for each.
var table = context.getPanel(panelName, true).table;
var hiddenCols = table.getAttribute("hiddenCols");
var lastVisibleIndex;
var visibleColCount = 0;
// Iterate all columns except of the first one for breakpoints.
var header = getAncestorByClass(target, "netHeaderRow");
var columns = cloneArray(header.childNodes);
columns.shift();
for (var i=0; i<columns.length; i++)
{
var column = columns[i];
var visible = (hiddenCols.indexOf(column.id) == -1);
items.push({
label: column.textContent,
type: "checkbox",
checked: visible,
nol10n: true,
command: bindFixed(this.onShowColumn, this, context, column.id)
});
if (visible)
{
lastVisibleIndex = i;
visibleColCount++;
}
}
// If the last column is visible, disable its menu item.
if (visibleColCount == 1)
items[lastVisibleIndex].disabled = true;
items.push("-");
items.push({
label: $STR("net.header.Reset_Header"),
nol10n: true,
command: bindFixed(this.onResetColumns, this, context)
});
return items;
},
onShowColumn: function(context, colId)
{
var table = context.getPanel(panelName, true).table;
var hiddenCols = table.getAttribute("hiddenCols");
// If the column is already presented in the list of hidden columns,
// remove it, otherwise append.
var index = hiddenCols.indexOf(colId);
if (index >= 0)
{
table.setAttribute("hiddenCols", hiddenCols.substr(0,index-1) +
hiddenCols.substr(index+colId.length));
}
else
{
table.setAttribute("hiddenCols", hiddenCols + " " + colId);
}
// Store current state into the preferences.
Firebug.setPref(Firebug.prefDomain, "net.hiddenColumns", table.getAttribute("hiddenCols"));
},
onResetColumns: function(context)
{
var panel = context.getPanel(panelName, true);
var header = panel.panelNode.getElementsByClassName("netHeaderRow").item(0);
// Reset widths
var columns = header.childNodes;
for (var i=0; i<columns.length; i++)
{
var col = columns[i];
if (col.style)
col.style.width = "";
}
// Reset visibility. Only the Status column is hidden by default.
panel.table.setAttribute("hiddenCols", "colStatus");
Firebug.setPref(Firebug.prefDomain, "net.hiddenColumns", "colStatus");
},
});
var NetRequestTable = Firebug.NetMonitor.NetRequestTable;
// ************************************************************************************************
/**
* @domplate Represents a template that is used to render net panel entries.
*/
Firebug.NetMonitor.NetRequestEntry = domplate(Firebug.Rep, new Firebug.Listener(),
{
fileTag:
FOR("file", "$files",
TR({"class": "netRow $file.file|getCategory focusRow outerFocusRow",
onclick: "$onClick", "role": "row", "aria-expanded": "false",
$hasHeaders: "$file.file|hasResponseHeaders",
$history: "$file.file.history",
$loaded: "$file.file.loaded",
$responseError: "$file.file|isError",
$fromCache: "$file.file.fromCache",
$inFrame: "$file.file|getInFrame"},
TD({"class": "netCol"},
DIV({"class": "sourceLine netRowHeader",
onclick: "$onClickRowHeader"},
" "
)
),
TD({"class": "netHrefCol netCol a11yFocus", "role": "rowheader"},
DIV({"class": "netHrefLabel netLabel",
style: "margin-left: $file.file|getIndent\\px"},
"$file.file|getHref"
),
DIV({"class": "netFullHrefLabel netHrefLabel",
style: "margin-left: $file.file|getIndent\\px"},
"$file.file.href"
)
),
TD({"class": "netStatusCol netCol a11yFocus", "role": "gridcell"},
DIV({"class": "netStatusLabel netLabel"}, "$file.file|getStatus")
),
TD({"class": "netDomainCol netCol a11yFocus", "role": "gridcell" },
DIV({"class": "netDomainLabel netLabel"}, "$file.file|getDomain")
),
TD({"class": "netSizeCol netCol a11yFocus", "role": "gridcell", "aria-describedby": "fbNetSizeInfoTip"},
DIV({"class": "netSizeLabel netLabel"}, "$file.file|getSize")
),
TD({"class": "netTimeCol netCol a11yFocus", "role": "gridcell", "aria-describedby": "fbNetTimeInfoTip" },
DIV({"class": "netLoadingIcon"}),
DIV({"class": "netBar"},
" ",
DIV({"class": "netBlockingBar", style: "left: $file.offset"}),
DIV({"class": "netResolvingBar", style: "left: $file.offset"}),
DIV({"class": "netConnectingBar", style: "left: $file.offset"}),
DIV({"class": "netSendingBar", style: "left: $file.offset"}),
DIV({"class": "netWaitingBar", style: "left: $file.offset"}),
DIV({"class": "netContentLoadBar", style: "left: $file.offset"}),
DIV({"class": "netWindowLoadBar", style: "left: $file.offset"}),
DIV({"class": "netReceivingBar", style: "left: $file.offset; width: $file.width"},
SPAN({"class": "netTimeLabel"}, "$file|getElapsedTime")
)
)
)
)
),
headTag:
TR({"class": "netHeadRow"},
TD({"class": "netHeadCol", colspan: 6},
DIV({"class": "netHeadLabel"}, "$doc.rootFile.href")
)
),
netInfoTag:
TR({"class": "netInfoRow outerFocusRow", "role" : "row"},
TD({"class": "sourceLine netRowHeader"}),
TD({"class": "netInfoCol", colspan: 5, "role" : "gridcell"})
),
activationTag:
TR({"class": "netRow netActivationRow"},
TD({"class": "netCol netActivationLabel", colspan: 6, "role": "status"},
$STR("net.ActivationMessage")
)
),
summaryTag:
TR({"class": "netRow netSummaryRow focusRow outerFocusRow", "role": "row", "aria-live": "polite"},
TD({"class": "netCol"}, " "),
TD({"class": "netCol netHrefCol a11yFocus", "role" : "rowheader"},
DIV({"class": "netCountLabel netSummaryLabel"}, "-")
),
TD({"class": "netCol netStatusCol a11yFocus", "role" : "gridcell"}),
TD({"class": "netCol netDomainCol a11yFocus", "role" : "gridcell"}),
TD({"class": "netTotalSizeCol netCol netSizeCol a11yFocus", "role" : "gridcell"},
DIV({"class": "netTotalSizeLabel netSummaryLabel"}, "0KB")
),
TD({"class": "netTotalTimeCol netCol netTimeCol a11yFocus", "role" : "gridcell"},
DIV({"class": "netSummaryBar", style: "width: 100%"},
DIV({"class": "netCacheSizeLabel netSummaryLabel", collapsed: "true"},
"(",
SPAN("0KB"),
SPAN(" " + $STR("FromCache")),
")"
),
DIV({"class": "netTimeBar"},
SPAN({"class": "netTotalTimeLabel netSummaryLabel"}, "0ms")
)
)
)
),
onClickRowHeader: function(event)
{
cancelEvent(event);
var rowHeader = event.target;
if (!hasClass(rowHeader, "netRowHeader"))
return;
var row = getAncestorByClass(event.target, "netRow");
if (!row)
return;
var context = Firebug.getElementPanel(row).context;
var panel = context.getPanel(panelName, true);
if (panel)
panel.breakOnRequest(row.repObject);
},
onClick: function(event)
{
if (isLeftClick(event))
{
var row = getAncestorByClass(event.target, "netRow");
if (row)
{
// Click on the rowHeader element inserts a breakpoint.
if (getAncestorByClass(event.target, "netRowHeader"))
return;
this.toggleHeadersRow(row);
cancelEvent(event);
}
}
},
toggleHeadersRow: function(row)
{
if (!hasClass(row, "hasHeaders"))
return;
var file = row.repObject;
toggleClass(row, "opened");
if (hasClass(row, "opened"))
{
var netInfoRow = this.netInfoTag.insertRows({}, row)[0];
var netInfoCol = netInfoRow.getElementsByClassName("netInfoCol").item(0);
var netInfoBox = NetInfoBody.tag.replace({file: file}, netInfoCol);
// Notify listeners so additional tabs can be created.
dispatch(NetInfoBody.fbListeners, "initTabBody", [netInfoBox, file]);
NetInfoBody.selectTabByName(netInfoBox, "Headers");
var category = Utils.getFileCategory(row.repObject);
if (category)
setClass(netInfoBox, "category-" + category);
row.setAttribute('aria-expanded', 'true');
}
else
{
var netInfoRow = row.nextSibling;
var netInfoBox = netInfoRow.getElementsByClassName("netInfoBody").item(0);
dispatch(NetInfoBody.fbListeners, "destroyTabBody", [netInfoBox, file]);
row.parentNode.removeChild(netInfoRow);
row.setAttribute('aria-expanded', 'false');
}
},
getCategory: function(file)
{
var category = Utils.getFileCategory(file);
if (category)
return "category-" + category;
return "category-undefined";
},
getInFrame: function(file)
{
return !!file.document.parent;
},
getIndent: function(file)
{
// XXXjoe Turn off indenting for now, it's confusing since we don't
// actually place nested files directly below their parent
//return file.document.level * indentWidth;
return 10;
},
isError: function(file)
{
if (file.aborted)
return true;
var errorRange = Math.floor(file.responseStatus/100);
return errorRange == 4 || errorRange == 5;
},
getHref: function(file)
{
return (file.method ? file.method.toUpperCase() : "?") + " " + getFileName(file.href);
},
getStatus: function(file)
{
var text = "";
if (file.responseStatus)
text += file.responseStatus + " ";
if (file.responseStatusText)
text += file.responseStatusText;
return text ? text : " ";
},
getDomain: function(file)
{
return getPrettyDomain(file.href);
},
getSize: function(file)
{
return this.formatSize(file.size);
},
getElapsedTime: function(file)
{
if (!file.elapsed || file.elapsed < 0)
return "";
return this.formatTime(file.elapsed);
},
hasResponseHeaders: function(file)
{
return !!file.responseHeaders;
},
formatSize: function(bytes)
{
return formatSize(bytes);
},
formatTime: function(elapsed)
{
// Use formatTime util from the lib.
return formatTime(elapsed);
}
});
var NetRequestEntry = Firebug.NetMonitor.NetRequestEntry;
// ************************************************************************************************
Firebug.NetMonitor.NetPage = domplate(Firebug.Rep,
{
separatorTag:
TR({"class": "netRow netPageSeparatorRow"},
TD({"class": "netCol netPageSeparatorLabel", colspan: 6, "role": "separator"})
),
pageTag:
TR({"class": "netRow netPageRow", onclick: "$onPageClick"},
TD({"class": "netCol netPageCol", colspan: 6, "role": "separator"},
DIV({"class": "netLabel netPageLabel netPageTitle"}, "$page|getTitle")
)
),
getTitle: function(page)
{
return page.pageTitle;
},
onPageClick: function(event)
{
if (!isLeftClick(event))
return;
var target = event.target;
var pageRow = getAncestorByClass(event.target, "netPageRow");
var panel = Firebug.getElementPanel(pageRow);
if (!hasClass(pageRow, "opened"))
{
setClass(pageRow, "opened");
var files = pageRow.files;
// Move all net-rows from the persistedState to this panel.
panel.insertRows(files, pageRow);
for (var i=0; i<files.length; i++)
panel.queue.push(files[i].file);
panel.layout();
}
else
{
removeClass(pageRow, "opened");
var nextRow = pageRow.nextSibling;
while (!hasClass(nextRow, "netPageRow") && !hasClass(nextRow, "netPageSeparatorRow"))
{
var nextSibling = nextRow.nextSibling;
nextRow.parentNode.removeChild(nextRow);
nextRow = nextSibling;
}
}
},
});
var NetPage = Firebug.NetMonitor.NetPage;
// ************************************************************************************************
/**
* @domplate Represents a template that is used to reneder detailed info about a request.
* This template is rendered when a request is expanded.
*/
Firebug.NetMonitor.NetInfoBody = domplate(Firebug.Rep, new Firebug.Listener(),
{
tag:
DIV({"class": "netInfoBody", _repObject: "$file"},
TAG("$infoTabs", {file: "$file"}),
TAG("$infoBodies", {file: "$file"})
),
infoTabs:
DIV({"class": "netInfoTabs focusRow subFocusRow", "role": "tablist"},
A({"class": "netInfoParamsTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab",
view: "Params",
$collapsed: "$file|hideParams"},
$STR("URLParameters")
),
A({"class": "netInfoHeadersTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab",
view: "Headers"},
$STR("Headers")
),
A({"class": "netInfoPostTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab",
view: "Post",
$collapsed: "$file|hidePost"},
$STR("Post")
),
A({"class": "netInfoPutTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab",
view: "Put",
$collapsed: "$file|hidePut"},
$STR("Put")
),
A({"class": "netInfoResponseTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab",
view: "Response",
$collapsed: "$file|hideResponse"},
$STR("Response")
),
A({"class": "netInfoCacheTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab",
view: "Cache",
$collapsed: "$file|hideCache"},
$STR("Cache")
),
A({"class": "netInfoHtmlTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab",
view: "Html",
$collapsed: "$file|hideHtml"},
$STR("HTML")
)
),
infoBodies:
DIV({"class": "netInfoBodies outerFocusRow"},
TABLE({"class": "netInfoParamsText netInfoText netInfoParamsTable", "role": "tabpanel",
cellpadding: 0, cellspacing: 0}, TBODY()),
DIV({"class": "netInfoHeadersText netInfoText", "role": "tabpanel"}),
DIV({"class": "netInfoPostText netInfoText", "role": "tabpanel"}),
DIV({"class": "netInfoPutText netInfoText", "role": "tabpanel"}),
DIV({"class": "netInfoResponseText netInfoText", "role": "tabpanel"}),
DIV({"class": "netInfoCacheText netInfoText", "role": "tabpanel"},
TABLE({"class": "netInfoCacheTable", cellpadding: 0, cellspacing: 0, "role": "presentation"},
TBODY({"role": "list", "aria-label": $STR("Cache")})
)
),
DIV({"class": "netInfoHtmlText netInfoText", "role": "tabpanel"},
IFRAME({"class": "netInfoHtmlPreview", "role": "document"})
)
),
headerDataTag:
FOR("param", "$headers",
TR({"role": "listitem"},
TD({"class": "netInfoParamName", "role": "presentation"},
TAG("$param|getNameTag", {param: "$param"})
),
TD({"class": "netInfoParamValue", "role": "list", "aria-label": "$param.name"},
FOR("line", "$param|getParamValueIterator",
CODE({"class": "focusRow subFocusRow", "role": "listitem"}, "$line")
)
)
)
),
customTab:
A({"class": "netInfo$tabId\\Tab netInfoTab", onclick: "$onClickTab", view: "$tabId", "role": "tab"},
"$tabTitle"
),
customBody:
DIV({"class": "netInfo$tabId\\Text netInfoText", "role": "tabpanel"}),
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
nameTag:
SPAN("$param|getParamName"),
nameWithTooltipTag:
SPAN({title: "$param.name"}, "$param|getParamName"),
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
getNameTag: function(param)
{
return (this.getParamName(param) == param.name) ? this.nameTag : this.nameWithTooltipTag;
},
getParamName: function(param)
{
var limit = 25;
var name = param.name;
if (name.length > limit)
name = name.substr(0, limit) + "...";
return name;
},
getParamTitle: function(param)
{
var limit = 25;
var name = param.name;
if (name.length > limit)
return name;
return "";
},
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
hideParams: function(file)
{
return !file.urlParams || !file.urlParams.length;
},
hidePost: function(file)
{
return file.method.toUpperCase() != "POST";
},
hidePut: function(file)
{
return file.method.toUpperCase() != "PUT";
},
hideResponse: function(file)
{
return file.category in binaryFileCategories;
},
hideCache: function(file)
{
//xxxHonza: I don't see any reason why not to display the cache also info for images.
return !file.cacheEntry/* || file.category=="image"*/;
},
hideHtml: function(file)
{
return (file.mimeType != "text/html") && (file.mimeType != "application/xhtml+xml");
},
onClickTab: function(event)
{
this.selectTab(event.currentTarget);
},
getParamValueIterator: function(param)
{
// This value is inserted into CODE element and so, make sure the HTML isn't escaped (1210).
// This is why the second parameter is true.
// The CODE (with style white-space:pre) element preserves whitespaces so they are
// displayed the same, as they come from the server (1194).
// In case of a long header values of post parameters the value must be wrapped (2105).
return wrapText(param.value, true);
},
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
appendTab: function(netInfoBox, tabId, tabTitle)
{
// Create new tab and body.
var args = {tabId: tabId, tabTitle: tabTitle};
this.customTab.append(args, netInfoBox.getElementsByClassName("netInfoTabs").item(0));
this.customBody.append(args, netInfoBox.getElementsByClassName("netInfoBodies").item(0));
},
selectTabByName: function(netInfoBox, tabName)
{
var tab = getChildByClass(netInfoBox, "netInfoTabs", "netInfo"+tabName+"Tab");
if (tab)
this.selectTab(tab);
},
selectTab: function(tab)
{
var netInfoBox = getAncestorByClass(tab, "netInfoBody");
var view = tab.getAttribute("view");
if (netInfoBox.selectedTab)
{
netInfoBox.selectedTab.removeAttribute("selected");
netInfoBox.selectedText.removeAttribute("selected");
netInfoBox.selectedTab.setAttribute("aria-selected", "false");
}
var textBodyName = "netInfo" + view + "Text";
netInfoBox.selectedTab = tab;
netInfoBox.selectedText = netInfoBox.getElementsByClassName(textBodyName).item(0);
netInfoBox.selectedTab.setAttribute("selected", "true");
netInfoBox.selectedText.setAttribute("selected", "true");
netInfoBox.selectedTab.setAttribute("aria-selected", "true");
var file = Firebug.getRepObject(netInfoBox);
var context = Firebug.getElementPanel(netInfoBox).context;
this.updateInfo(netInfoBox, file, context);
},
updateInfo: function(netInfoBox, file, context)
{
if (!netInfoBox)
{
return;
}
var tab = netInfoBox.selectedTab;
if (hasClass(tab, "netInfoParamsTab"))
{
if (file.urlParams && !netInfoBox.urlParamsPresented)
{
netInfoBox.urlParamsPresented = true;
this.insertHeaderRows(netInfoBox, file.urlParams, "Params");
}
}
if (hasClass(tab, "netInfoHeadersTab"))
{
var headersText = netInfoBox.getElementsByClassName("netInfoHeadersText").item(0);
if (file.responseHeaders && !netInfoBox.responseHeadersPresented)
{
netInfoBox.responseHeadersPresented = true;
NetInfoHeaders.renderHeaders(headersText, file.responseHeaders, "ResponseHeaders");
}
if (file.requestHeaders && !netInfoBox.requestHeadersPresented)
{
netInfoBox.requestHeadersPresented = true;
NetInfoHeaders.renderHeaders(headersText, file.requestHeaders, "RequestHeaders");
}
}
if (hasClass(tab, "netInfoPostTab"))
{
if (!netInfoBox.postPresented)
{
netInfoBox.postPresented = true;
var postText = netInfoBox.getElementsByClassName("netInfoPostText").item(0);
NetInfoPostData.render(context, postText, file);
}
}
if (hasClass(tab, "netInfoPutTab"))
{
if (!netInfoBox.putPresented)
{
netInfoBox.putPresented = true;
var putText = netInfoBox.getElementsByClassName("netInfoPutText").item(0);
NetInfoPostData.render(context, putText, file);
}
}
if (hasClass(tab, "netInfoResponseTab") && file.loaded && !netInfoBox.responsePresented)
{
var responseTextBox = netInfoBox.getElementsByClassName("netInfoResponseText").item(0);
if (file.category == "image")
{
netInfoBox.responsePresented = true;
var responseImage = netInfoBox.ownerDocument.createElement("img");
responseImage.src = file.href;
clearNode(responseTextBox);
responseTextBox.appendChild(responseImage, responseTextBox);
}
else if (!(binaryCategoryMap.hasOwnProperty(file.category)))
{
this.setResponseText(file, netInfoBox, responseTextBox, context);
}
}
if (hasClass(tab, "netInfoCacheTab") && file.loaded && !netInfoBox.cachePresented)
{
var responseTextBox = netInfoBox.getElementsByClassName("netInfoCacheText").item(0);
if (file.cacheEntry) {
netInfoBox.cachePresented = true;
this.insertHeaderRows(netInfoBox, file.cacheEntry, "Cache");
}
}
if (hasClass(tab, "netInfoHtmlTab") && file.loaded && !netInfoBox.htmlPresented)
{
netInfoBox.htmlPresented = true;
var text = Utils.getResponseText(file, context);
var iframe = netInfoBox.getElementsByClassName("netInfoHtmlPreview").item(0);
iframe.contentWindow.document.body.innerHTML = text;
}
// Notify listeners about update so, content of custom tabs can be updated.
dispatch(NetInfoBody.fbListeners, "updateTabBody", [netInfoBox, file, context]);
},
setResponseText: function(file, netInfoBox, responseTextBox, context)
{
// Get response text and make sure it doesn't exceed the max limit.
var text = Utils.getResponseText(file, context);
var limit = Firebug.netDisplayedResponseLimit + 15;
var limitReached = text ? (text.length > limit) : false;
if (limitReached)
text = text.substr(0, limit) + "...";
// Insert the response into the UI.
if (text)
insertWrappedText(text, responseTextBox);
else
insertWrappedText("", responseTextBox);
// Append a message informing the user that the response isn't fully displayed.
if (limitReached)
{
var object = {
text: $STR("net.responseSizeLimitMessage"),
onClickLink: function() {
var panel = context.getPanel("net", true);
panel.openResponseInTab(file);
}
};
Firebug.NetMonitor.ResponseSizeLimit.append(object, responseTextBox);
}
netInfoBox.responsePresented = true;
},
insertHeaderRows: function(netInfoBox, headers, tableName, rowName)
{
if (!headers.length)
return;
var headersTable = netInfoBox.getElementsByClassName("netInfo"+tableName+"Table").item(0);
var tbody = getChildByClass(headersTable, "netInfo" + rowName + "Body");
if (!tbody)
tbody = headersTable.firstChild;
var titleRow = getChildByClass(tbody, "netInfo" + rowName + "Title");
this.headerDataTag.insertRows({headers: headers}, titleRow ? titleRow : tbody);
removeClass(titleRow, "collapsed");
},
});
var NetInfoBody = Firebug.NetMonitor.NetInfoBody;
// ************************************************************************************************
/**
* @domplate Represents posted data within request info (the info, which is visible when
* a request entry is expanded. This template renders content of the Post tab.
*/
Firebug.NetMonitor.NetInfoPostData = domplate(Firebug.Rep, new Firebug.Listener(),
{
// application/x-www-form-urlencoded
paramsTable:
TABLE({"class": "netInfoPostParamsTable", cellpadding: 0, cellspacing: 0, "role": "presentation"},
TBODY({"role": "list", "aria-label": $STR("net.label.Parameters")},
TR({"class": "netInfoPostParamsTitle", "role": "presentation"},
TD({colspan: 2, "role": "presentation"},
DIV({"class": "netInfoPostParams"},
$STR("net.label.Parameters"),
SPAN({"class": "netInfoPostContentType"},
"application/x-www-form-urlencoded"
)
)
)
)
)
),
// multipart/form-data
partsTable:
TABLE({"class": "netInfoPostPartsTable", cellpadding: 0, cellspacing: 0, "role": "presentation"},
TBODY({"role": "list", "aria-label": $STR("net.label.Parts")},
TR({"class": "netInfoPostPartsTitle", "role": "presentation"},
TD({colspan: 2, "role":"presentation" },
DIV({"class": "netInfoPostParams"},
$STR("net.label.Parts"),
SPAN({"class": "netInfoPostContentType"},
"multipart/form-data"
)
)
)
)
)
),
// application/json
jsonTable:
TABLE({"class": "netInfoPostJSONTable", cellpadding: 0, cellspacing: 0, "role": "presentation"},
TBODY({"role": "list", "aria-label": $STR("jsonviewer.tab.JSON")},
TR({"class": "netInfoPostJSONTitle", "role": "presentation"},
TD({"role": "presentation" },
DIV({"class": "netInfoPostParams"},
$STR("jsonviewer.tab.JSON")
)
)
),
TR(
TD({"class": "netInfoPostJSONBody"})
)
)
),
// application/xml
xmlTable:
TABLE({"class": "netInfoPostXMLTable", cellpadding: 0, cellspacing: 0, "role": "presentation"},
TBODY({"role": "list", "aria-label": $STR("xmlviewer.tab.XML")},
TR({"class": "netInfoPostXMLTitle", "role": "presentation"},
TD({"role": "presentation" },
DIV({"class": "netInfoPostParams"},
$STR("xmlviewer.tab.XML")
)
)
),
TR(
TD({"class": "netInfoPostXMLBody"})
)
)
),
sourceTable:
TABLE({"class": "netInfoPostSourceTable", cellpadding: 0, cellspacing: 0, "role": "presentation"},
TBODY({"role": "list", "aria-label": $STR("net.label.Source")},
TR({"class": "netInfoPostSourceTitle", "role": "presentation"},
TD({colspan: 2, "role": "presentation"},
DIV({"class": "netInfoPostSource"},
$STR("net.label.Source")
)
)
)
)
),
sourceBodyTag:
TR({"role": "presentation"},
TD({colspan: 2, "role": "presentation"},
FOR("line", "$param|getParamValueIterator",
CODE({"class":"focusRow subFocusRow" , "role": "listitem"},"$line")
)
)
),
getParamValueIterator: function(param)
{
return NetInfoBody.getParamValueIterator(param);
},
render: function(context, parentNode, file)
{
var text = Utils.getPostText(file, context, true);
if (text == undefined)
return;
if (Utils.isURLEncodedRequest(file, context))
{
var lines = text.split("\n");
var params = parseURLEncodedText(lines[lines.length-1]);
if (params)
this.insertParameters(parentNode, params);
}
if (Utils.isMultiPartRequest(file, context))
{
var data = this.parseMultiPartText(file, context);
if (data)
this.insertParts(parentNode, data);
}
var contentType = Utils.findHeader(file.requestHeaders, "content-type");
if (Firebug.JSONViewerModel.isJSON(contentType))
this.insertJSON(parentNode, file, context);
if (Firebug.XMLViewerModel.isXML(contentType))
this.insertXML(parentNode, file, context);
var postText = Utils.getPostText(file, context);
postText = Utils.formatPostText(postText);
if (postText)
this.insertSource(parentNode, postText);
},
insertParameters: function(parentNode, params)
{
if (!params || !params.length)
return;
var paramTable = this.paramsTable.append(null, parentNode);
var row = paramTable.getElementsByClassName("netInfoPostParamsTitle").item(0);
NetInfoBody.headerDataTag.insertRows({headers: params}, row);
},
insertParts: function(parentNode, data)
{
if (!data.params || !data.params.length)
return;
var partsTable = this.partsTable.append(null, parentNode);
var row = partsTable.getElementsByClassName("netInfoPostPartsTitle").item(0);
NetInfoBody.headerDataTag.insertRows({headers: data.params}, row);
},
insertJSON: function(parentNode, file, context)
{
var text = Utils.getPostText(file, context);
var data = parseJSONString(text, "http://" + file.request.originalURI.host);
if (!data)
return;
var jsonTable = this.jsonTable.append(null, parentNode);
var jsonBody = jsonTable.getElementsByClassName("netInfoPostJSONBody").item(0);
if (!this.toggles)
this.toggles = {};
Firebug.DOMPanel.DirTable.tag.replace(
{object: data, toggles: this.toggles}, jsonBody);
},
insertXML: function(parentNode, file, context)
{
var text = Utils.getPostText(file, context);
var jsonTable = this.xmlTable.append(null, parentNode);
var jsonBody = jsonTable.getElementsByClassName("netInfoPostXMLBody").item(0);
Firebug.XMLViewerModel.insertXML(jsonBody, text);
},
insertSource: function(parentNode, text)
{
var sourceTable = this.sourceTable.append(null, parentNode);
var row = sourceTable.getElementsByClassName("netInfoPostSourceTitle").item(0);
var param = {value: text};
this.sourceBodyTag.insertRows({param: param}, row);
},
parseMultiPartText: function(file, context)
{
var text = Utils.getPostText(file, context);
if (text == undefined)
return null;
FBTrace.sysout("net.parseMultiPartText; boundary: ", text);
var boundary = text.match(/\s*boundary=\s*(.*)/)[1];
var divider = "\r\n\r\n";
var bodyStart = text.indexOf(divider);
var body = text.substr(bodyStart + divider.length);
var postData = {};
postData.mimeType = "multipart/form-data";
postData.params = [];
var parts = body.split("--" + boundary);
for (var i=0; i<parts.length; i++)
{
var part = parts[i].split(divider);
if (part.length != 2)
continue;
var m = part[0].match(/\s*name=\"(.*)\"(;|$)/);
postData.params.push({
name: (m && m.length > 1) ? m[1] : "",
value: trim(part[1])
})
}
return postData;
}
});
var NetInfoPostData = Firebug.NetMonitor.NetInfoPostData;
// ************************************************************************************************
/**
* @domplate Used within the Net panel to display raw source of request and response headers
* as well as pretty-formatted summary of these headers.
*/
Firebug.NetMonitor.NetInfoHeaders = domplate(Firebug.Rep, new Firebug.Listener(),
{
tag:
DIV({"class": "netInfoHeadersTable", "role": "tabpanel"},
DIV({"class": "netInfoHeadersGroup netInfoResponseHeadersTitle"},
SPAN($STR("ResponseHeaders")),
SPAN({"class": "netHeadersViewSource response collapsed", onclick: "$onViewSource",
_sourceDisplayed: false, _rowName: "ResponseHeaders"},
$STR("net.headers.view source")
)
),
TABLE({cellpadding: 0, cellspacing: 0},
TBODY({"class": "netInfoResponseHeadersBody", "role": "list",
"aria-label": $STR("ResponseHeaders")})
),
DIV({"class": "netInfoHeadersGroup netInfoRequestHeadersTitle"},
SPAN($STR("RequestHeaders")),
SPAN({"class": "netHeadersViewSource request collapsed", onclick: "$onViewSource",
_sourceDisplayed: false, _rowName: "RequestHeaders"},
$STR("net.headers.view source")
)
),
TABLE({cellpadding: 0, cellspacing: 0},
TBODY({"class": "netInfoRequestHeadersBody", "role": "list",
"aria-label": $STR("RequestHeaders")})
)
),
sourceTag:
TR({"role": "presentation"},
TD({colspan: 2, "role": "presentation"},
PRE({"class": "source"})
)
),
onViewSource: function(event)
{
var target = event.target;
var requestHeaders = (target.rowName == "RequestHeaders");
var netInfoBox = getAncestorByClass(target, "netInfoBody");
var file = netInfoBox.repObject;
if (target.sourceDisplayed)
{
var headers = requestHeaders ? file.requestHeaders : file.responseHeaders;
this.insertHeaderRows(netInfoBox, headers, target.rowName);
target.innerHTML = $STR("net.headers.view source");
}
else
{
var source = requestHeaders ? file.requestHeadersText : file.responseHeadersText;
this.insertSource(netInfoBox, source, target.rowName);
target.innerHTML = $STR("net.headers.pretty print");
}
target.sourceDisplayed = !target.sourceDisplayed;
cancelEvent(event);
},
insertSource: function(netInfoBox, source, rowName)
{
// This breaks copy to clipboard.
//if (source)
// source = source.replace(/\r\n/gm, "<span style='color:lightgray'>\\r\\n</span>\r\n");
var tbody = netInfoBox.getElementsByClassName("netInfo" + rowName + "Body").item(0);
var node = this.sourceTag.replace({}, tbody);
var sourceNode = node.getElementsByClassName("source").item(0);
sourceNode.innerHTML = source;
},
insertHeaderRows: function(netInfoBox, headers, rowName)
{
var headersTable = netInfoBox.getElementsByClassName("netInfoHeadersTable").item(0);
var tbody = headersTable.getElementsByClassName("netInfo" + rowName + "Body").item(0);
clearNode(tbody);
if (!headers.length)
return;
NetInfoBody.headerDataTag.insertRows({headers: headers}, tbody);
var titleRow = getChildByClass(headersTable, "netInfo" + rowName + "Title");
removeClass(titleRow, "collapsed");
},
init: function(parent)
{
var rootNode = this.tag.append({}, parent);
var netInfoBox = getAncestorByClass(parent, "netInfoBody");
var file = netInfoBox.repObject;
var viewSource;
viewSource = rootNode.getElementsByClassName("netHeadersViewSource request").item(0);
if (file.requestHeadersText)
removeClass(viewSource, "collapsed");
viewSource = rootNode.getElementsByClassName("netHeadersViewSource response").item(0);
if (file.responseHeadersText)
removeClass(viewSource, "collapsed");
},
renderHeaders: function(parent, headers, rowName)
{
if (!parent.firstChild)
this.init(parent);
this.insertHeaderRows(parent, headers, rowName);
}
});
var NetInfoHeaders = Firebug.NetMonitor.NetInfoHeaders;
// ************************************************************************************************
/**
* @domplate Represents a template for popup tip that displays detailed timing info about
* a network request.
*/
Firebug.NetMonitor.TimeInfoTip = domplate(Firebug.Rep,
{
tableTag:
TABLE({"class": "timeInfoTip", "id": "fbNetTimeInfoTip"},
TBODY()
),
timingsTag:
FOR("time", "$timings",
TR({"class": "timeInfoTipRow", $collapsed: "$time|hideBar"},
TD({"class": "$time|getBarClass timeInfoTipBar",
$loaded: "$time.loaded",
$fromCache: "$time.fromCache",
}),
TD({"class": "timeInfoTipCell startTime"},
"$time.start|formatStartTime"
),
TD({"class": "timeInfoTipCell elapsedTime"},
"$time.elapsed|formatTime"
),
TD("$time|getLabel")
)
),
startTimeTag:
TR(
TD(),
TD("$startTime.time|formatStartTime"),
TD({"colspan": 2},
"$startTime|getLabel"
)
),
separatorTag:
TR(
TD({"colspan": 4, "height": "10px"})
),
eventsTag:
FOR("event", "$events",
TR({"class": "timeInfoTipEventRow"},
TD({"class": "timeInfoTipBar", align: "center"},
DIV({"class": "$event|getBarClass timeInfoTipEventBar"})
),
TD("$event.start|formatStartTime"),
TD({"colspan": 2},
"$event|getLabel"
)
)
),
hideBar: function(obj)
{
return !obj.elapsed && obj.bar == "Blocking";
},
getBarClass: function(obj)
{
return "net" + obj.bar + "Bar";
},
formatTime: function(time)
{
return formatTime(time)
},
formatStartTime: function(time)
{
var label = formatTime(time);
if (!time)
return label;
return (time > 0 ? "+" : "") + label;
},
getLabel: function(obj)
{
return $STR("requestinfo." + obj.bar);
},
render: function(file, parentNode)
{
var infoTip = Firebug.NetMonitor.TimeInfoTip.tableTag.replace({}, parentNode);
var elapsed = file.loaded ? file.endTime - file.startTime : file.phase.phaseEndTime - file.startTime;
var blockingEnd = (file.sendingTime > file.startTime) ? file.sendingTime : file.waitingForTime;
var timings = [];
timings.push({bar: "Resolving",
elapsed: file.connectingTime - file.startTime,
start: 0});
timings.push({bar: "Connecting",
elapsed: file.connectedTime - file.connectingTime,
start: file.connectingTime - file.startTime});
timings.push({bar: "Blocking",
elapsed: blockingEnd - file.connectedTime,
start: file.connectedTime - file.startTime});
// In Fx3.6 the STATUS_SENDING_TO is always fired (nsIActivityDistributor)
// In Fx3.5 the STATUS_SENDING_TO (nsIWebProgressListener) doesn't have to come
// This workaround is for 3.5
var sendElapsed = file.sendStarted ? file.waitingForTime - file.sendingTime : 0;
var sendStarted = timings[0].elapsed + timings[1].elapsed + timings[2].elapsed;
timings.push({bar: "Sending",
elapsed: sendElapsed,
start: file.sendStarted ? file.sendingTime - file.startTime : sendStarted});
timings.push({bar: "Waiting",
elapsed: file.respondedTime - file.waitingForTime,
start: file.waitingForTime - file.startTime});
timings.push({bar: "Receiving",
elapsed: file.endTime - file.respondedTime,
start: file.respondedTime - file.startTime,
loaded: file.loaded, fromCache: file.fromCache});
var events = [];
if (file.phase.contentLoadTime)
events.push({bar: "ContentLoad", start: file.phase.contentLoadTime - file.startTime});
if (file.phase.windowLoadTime)
events.push({bar: "WindowLoad", start: file.phase.windowLoadTime - file.startTime});
// Insert start request time.
var startTime = {};
startTime.time = file.startTime - file.phase.startTime;
startTime.bar = "Started";
this.startTimeTag.insertRows({startTime: startTime}, infoTip.firstChild);
// Insert separator.
this.separatorTag.insertRows({}, infoTip.firstChild);
// Insert request timing info.
this.timingsTag.insertRows({timings: timings}, infoTip.firstChild);
// Insert events timing info.
if (events.length)
{
this.separatorTag.insertRows({}, infoTip.firstChild);
this.eventsTag.insertRows({events: events}, infoTip.firstChild);
}
return true;
}
});
// ************************************************************************************************
/**
* @domplate Represents a template for a pupup tip with detailed size info.
*/
Firebug.NetMonitor.SizeInfoTip = domplate(Firebug.Rep,
{
tag:
TABLE({"class": "sizeInfoTip", "id": "fbNetSizeInfoTip"},
TBODY(
FOR("size", "$sizeInfo",
TAG("$size|getRowTag", {size: "$size"})
)
)
),
sizeTag:
TR({"class": "sizeInfoRow", $collapsed: "$size|hideRow"},
TD({"class": "sizeInfoLabelCol"}, "$size.label"),
TD({"class": "sizeInfoSizeCol"}, "$size|formatSize"),
TD({"class": "sizeInfoDetailCol"}, "$size|formatNumber")
),
separatorTag:
TR(
TD({"colspan": 3, "height": "7px"})
),
descTag:
TR(
TD({"colspan": 3, "class": "sizeInfoDescCol"}, "$size.label")
),
getRowTag: function(size)
{
if (size.size == -2)
return this.descTag;
return (size.label == "-") ? this.separatorTag : this.sizeTag;
},
hideRow: function(size)
{
return size.size < 0;
},
formatSize: function(size)
{
return formatSize(size.size);
},
formatNumber: function(size)
{
return size.size ? ("(" + formatNumber(size.size) + ")") : "";
},
render: function(file, parentNode)
{
var postText = Utils.getPostText(file, FirebugContext, true);
postText = postText ? postText : "";
var sizeInfo = [];
sizeInfo.push({label: $STR("net.sizeinfo.Response Body"), size: file.size});
sizeInfo.push({label: $STR("net.sizeinfo.Post Body"), size: postText.length});
if (file.requestHeadersText)
{
var responseHeaders = file.responseHeadersText ? file.responseHeadersText : 0;
sizeInfo.push({label: "-", size: 0});
sizeInfo.push({label: $STR("net.sizeinfo.Total Received") + "*",
size: responseHeaders.length + file.size});
sizeInfo.push({label: $STR("net.sizeinfo.Total Sent") + "*",
size: file.requestHeadersText.length + postText.length});
sizeInfo.push({label: "*Including Headers", size: -2});
}
this.tag.replace({sizeInfo: sizeInfo}, parentNode);
},
});
// ************************************************************************************************
Firebug.NetMonitor.NetLimit = domplate(Firebug.Rep,
{
collapsed: true,
tableTag:
DIV(
TABLE({width: "100%", cellpadding: 0, cellspacing: 0},
TBODY()
)
),
limitTag:
TR({"class": "netRow netLimitRow", $collapsed: "$isCollapsed"},
TD({"class": "netCol netLimitCol", colspan: 6},
TABLE({cellpadding: 0, cellspacing: 0},
TBODY(
TR(
TD(
SPAN({"class": "netLimitLabel"},
$STRP("plural.Limit_Exceeded", [0])
)
),
TD({style: "width:100%"}),
TD(
BUTTON({"class": "netLimitButton", title: "$limitPrefsTitle",
onclick: "$onPreferences"},
$STR("LimitPrefs")
)
),
TD(" ")
)
)
)
)
),
isCollapsed: function()
{
return this.collapsed;
},
onPreferences: function(event)
{
openNewTab("about:config");
},
updateCounter: function(row)
{
removeClass(row, "collapsed");
// Update info within the limit row.
var limitLabel = row.getElementsByClassName("netLimitLabel").item(0);
limitLabel.firstChild.nodeValue = $STRP("plural.Limit_Exceeded", [row.limitInfo.totalCount]);
},
createTable: function(parent, limitInfo)
{
var table = this.tableTag.replace({}, parent);
var row = this.createRow(table.firstChild.firstChild, limitInfo);
return [table, row];
},
createRow: function(parent, limitInfo)
{
var row = this.limitTag.insertRows(limitInfo, parent, this)[0];
row.limitInfo = limitInfo;
return row;
},
// nsIPrefObserver
observe: function(subject, topic, data)
{
// We're observing preferences only.
if (topic != "nsPref:changed")
return;
if (data.indexOf("net.logLimit") != -1)
this.updateMaxLimit();
},
updateMaxLimit: function()
{
var value = Firebug.getPref(Firebug.prefDomain, "net.logLimit");
maxQueueRequests = value ? value : maxQueueRequests;
}
});
var NetLimit = Firebug.NetMonitor.NetLimit;
// ************************************************************************************************
Firebug.NetMonitor.ResponseSizeLimit = domplate(Firebug.Rep,
{
tag:
DIV({"class": "netInfoResponseSizeLimit"},
SPAN("$object.beforeLink"),
A({"class": "objectLink", onclick: "$onClickLink"},
"$object.linkText"
),
SPAN("$object.afterLink")
),
reLink: /^(.*)<a>(.*)<\/a>(.*$)/,
append: function(obj, parent)
{
var m = obj.text.match(this.reLink);
return this.tag.append({onClickLink: obj.onClickLink,
object: {
beforeLink: m[1],
linkText: m[2],
afterLink: m[3],
}}, parent, this);
}
});
// ************************************************************************************************
function NetProgress(context)
{
this.context = context;
this.breakpoints = new NetBreakpointGroup();
var panel = null;
var queue = [];
this.post = function(handler, args)
{
if (panel)
{
var file = handler.apply(this, args);
if (file)
{
panel.updateFile(file);
// If the panel isn't currently visible, make sure the limit is up to date.
if (!panel.layoutInterval)
panel.updateLogLimit(maxQueueRequests);
return file;
}
}
else
{
// The first page request is made before the initContext (known problem).
queue.push(handler, args);
}
};
this.flush = function()
{
for (var i=0; i<queue.length; i+=2)
this.post(queue[i], queue[i+1]);
queue = [];
};
this.activate = function(activePanel)
{
this.panel = panel = activePanel;
if (panel)
this.flush();
};
this.update = function(file)
{
if (panel)
panel.updateFile(file);
};
this.clear = function()
{
this.requests = [];
this.files = [];
this.phases = [];
this.documents = [];
this.windows = [];
this.currentPhase = null;
queue = [];
};
this.cacheListener = new NetCacheListener(this);
this.clear();
}
NetProgress.prototype =
{
panel: null,
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
startFile: function startFile(request, win)
{
var file = this.getRequestFile(request, win);
if (file)
{
// XXXjjb Honza I have to set these to get the conditional to work
file.urlParams = parseURLParams(file.href);
this.breakOnXHR(file);
}
},
requestedFile: function requestedFile(request, time, win, xhr)
{
var file = this.getRequestFile(request, win);
if (file)
{
file.startTime = file.endTime = time;
file.resolvingTime = time;
file.connectingTime = time;
file.connectedTime = time;
file.sendingTime = time;
file.waitingForTime = time;
file.respondedTime = time;
file.isXHR = xhr;
file.isBackground = request.loadFlags & LOAD_BACKGROUND;
file.method = request.requestMethod;
//file.urlParams = parseURLParams(file.href);
Utils.getPostText(file, this.context);
this.extendPhase(file);
dispatch(Firebug.NetMonitor.fbListeners, "onRequest", [this.context, file]);
return file;
}
else
{
}
},
breakOnXHR: function(file)
{
var halt = false;
var conditionIsFalse = false;
// If there is an enabled breakpoint with condition:
// 1) break if the condition is evaluated to true.
var breakpoints = this.context.netProgress.breakpoints;
var bp = breakpoints ? breakpoints.findBreakpoint(file.getFileURL()) : null;
if (bp && bp.checked)
{
halt = true;
if (bp.condition)
{
halt = bp.evaluateCondition(this.context, file);
conditionIsFalse = !halt;
}
}
// 2) If break on XHR flag is set and there is no condition evaluated to false,
// break with "break on next" breaking cause (this new breaking cause can override
// an existing one that is set when evaluating a breakpoint condition).
if (this.context.breakOnXHR && !conditionIsFalse)
{
this.context.breakingCause = {
title: $STR("net.Break On XHR"),
message: cropString(file.href, 200),
copyAction: bindFixed(copyToClipboard, FBL, file.href)
};
halt = true;
}
// Ignore if there is no reason to break.
if (!halt)
return;
// Even if the execution was stopped at breakpoint reset the global
// breakOnXHR flag.
this.context.breakOnXHR = false;
Firebug.Breakpoint.breakNow(this.context.getPanel(panelName, true));
},
requestHeadersFile: function(request, time, requestHeadersText)
{
var file = this.getRequestFile(request);
if (file)
file.requestHeadersText = requestHeadersText;
},
responseHeadersFile: function(request, time, responseHeadersText)
{
var file = this.getRequestFile(request);
if (file)
file.responseHeadersText = responseHeadersText;
},
completedFile: function completedFile(request, time)
{
var file = this.getRequestFile(request);
if (file)
{
file.respondedTime = time;
file.endTime = time;
return file;
}
},
respondedFile: function respondedFile(request, time, info)
{
dispatch(Firebug.NetMonitor.fbListeners, "onExamineResponse", [this.context, request]);
var file = this.getRequestFile(request);
if (file)
{
if (!Ci.nsIHttpActivityDistributor)
{
file.respondedTime = time;
file.endTime = time;
}
if (request.contentLength >= 0)
file.size = request.contentLength;
if (info.responseStatus == 304)
file.fromCache = true;
else if (!file.fromCache)
file.fromCache = false;
Utils.getHttpHeaders(request, file);
file.responseStatus = info.responseStatus;
file.responseStatusText = info.responseStatusText;
file.postText = info.postText;
file.aborted = false;
this.endLoad(file);
// Use ACTIVITY_SUBTYPE_RESPONSE_COMPLETE to get the info if possible.
if (!Ci.nsIHttpActivityDistributor)
{
if (file.fromCache)
getCacheEntry(file, this);
}
if (Firebug.showNetworkErrors && NetRequestEntry.isError(file))
{
Firebug.Errors.increaseCount(this.context);
var message = "NetworkError: " + NetRequestEntry.getStatus(file) + " - "+file.href;
Firebug.Console.log(message, this.context, "error", null, true, file.getFileLink(message));
}
dispatch(Firebug.NetMonitor.fbListeners, "onResponse", [this.context, file]);
return file;
}
},
respondedCacheFile: function respondedCacheFile(request, time)
{
var file = this.getRequestFile(request, null, true);
if (file)
{
this.panel.removeLogEntry(file, true);
}
else
{
}
},
waitingForFile: function waitingForFile(request, time)
{
var file = this.getRequestFile(request, null, true);
if (file)
{
if (!file.receivingStarted)
{
file.waitingForTime = time;
file.receivingStarted = true;
}
}
// Don't update the UI now (optimalization).
return null;
},
sendingFile: function sendingFile(request, time, size)
{
var file = this.getRequestFile(request, null, true);
if (file)
{
// Remember when the send started.
if (!file.sendStarted)
{
file.sendingTime = time;
file.sendStarted = true;
}
file.totalSent = size;
}
// Don't update the UI now (optimalization).
return null;
},
connectingFile: function connectingFile(request, time)
{
var file = this.getRequestFile(request, null, true);
if (file)
{
file.connectingTime = time;
file.connectedTime = time; // just in case connected_to would never came.
}
// Don't update the UI now (optimalization).
return null;
},
connectedFile: function connectedFile(request, time)
{
var file = this.getRequestFile(request, null, true);
if (file)
{
file.connectedTime = time;
}
// Don't update the UI now (optimalization).
return null;
},
receivingFile: function receivingFile(request, time, size)
{
var file = this.getRequestFile(request, null, true);
if (file)
{
file.endTime = time;
file.totalReceived = size;
// Update phase's lastFinishedFile in case of long time downloads.
// This forces the timeline to have proper extent.
if (file.phase && file.phase.endTime < time)
file.phase.lastFinishedFile = file;
// Force update UI.
if (file.row && hasClass(file.row, "opened"))
{
var netInfoBox = file.row.nextSibling.getElementsByClassName("netInfoBody").item(0);
if (netInfoBox)
{
netInfoBox.responsePresented = false;
netInfoBox.htmlPresented = false;
}
}
}
return file;
},
completeFile: function completeFile(request, time, responseSize)
{
var file = this.getRequestFile(request, null, true);
if (file)
{
if (responseSize > 0)
file.size = responseSize;
// This was only a helper to show download progress.
file.totalReceived = 0;
// The request is completed, get cache entry.
getCacheEntry(file, this);
}
return file;
},
closedFile: function closedFile(request, time)
{
var file = this.getRequestFile(request, null, true);
if (file)
{
if (!file.loaded && !file.responseHeadersText)
{
this.endLoad(file);
file.aborted = true;
file.responseStatusText = "Timeout";
file.respondedTime = time;
file.endTime = time;
}
}
return file;
},
resolvingFile: function resolvingFile(request, time)
{
var file = this.getRequestFile(request, null, true);
if (file)
{
file.resolvingTime = time;
}
return file;
},
progressFile: function progressFile(request, progress, expectedSize, time)
{
var file = this.getRequestFile(request, null, true);
if (file)
{
file.size = progress;
file.expectedSize = expectedSize;
file.endTime = time;
}
return file;
},
stopFile: function stopFile(request, time, postText, responseText)
{
var file = this.getRequestFile(request, null, true);
if (file)
{
if (file.endTime == file.startTime)
file.endTime = time;
file.postText = postText;
file.responseText = responseText;
Utils.getHttpHeaders(request, file);
this.endLoad(file);
getCacheEntry(file, this);
}
return file;
},
abortFile: function abortFile(request, time, postText, responseText)
{
var file = this.getRequestFile(request, null, true);
if (file)
{
file.aborted = true;
file.responseStatusText = "Aborted";
}
return this.stopFile(request, time, postText, responseText);
},
windowLoad: function windowLoad(window, time)
{
if (!this.phases.length)
return;
// Update all requests that belong to the first phase.
var firstPhase = this.phases[0];
firstPhase.windowLoadTime = time;
// Return the first file, so the layout is updated. I can happen that the
// onLoad event is the last one and the graph end-time must be recalculated.
return firstPhase.files[0];
},
contentLoad: function contentLoad(window, time)
{
if (!this.phases.length)
return;
// Update all requests that belong to the first phase.
var firstPhase = this.phases[0];
firstPhase.contentLoadTime = time;
return null;
},
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
getRequestFile: function getRequestFile(request, win, noCreate)
{
var name = safeGetName(request);
if (!name || reIgnore.exec(name))
return null;
var index = this.requests.indexOf(request);
if (index == -1 && noCreate)
return null;
if (index == -1)
{
if (!win || getRootWindow(win) != this.context.window)
return;
var fileDoc = this.getRequestDocument(win);
var isDocument = request.loadFlags & LOAD_DOCUMENT_URI && fileDoc.parent;
var doc = isDocument ? fileDoc.parent : fileDoc;
var file = doc.createFile(request);
if (isDocument)
{
fileDoc.documentFile = file;
file.ownDocument = fileDoc;
}
file.request = request;
this.requests.push(request);
this.files.push(file);
return file;
}
// There is already a file for the reqeust so use it.
return this.files[index];
},
getRequestDocument: function(win)
{
if (win)
{
var index = this.windows.indexOf(win);
if (index == -1)
{
var doc = new NetDocument();
if (win.parent != win)
doc.parent = this.getRequestDocument(win.parent);
//doc.level = getFrameLevel(win);
this.documents.push(doc);
this.windows.push(win);
return doc;
}
else
return this.documents[index];
}
else
return this.documents[0];
},
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
endLoad: function(file)
{
file.loaded = true;
// Update last finished file of the associated phase.
//xxxHonza: verify this.
if (file.phase)
file.phase.lastFinishedFile = file;
},
extendPhase: function(file)
{
if (this.currentPhase)
{
// If the new request has been started within a "phaseInterval" after the
// previous reqeust has been started, associate it with the current phase;
// otherwise create a new phase.
var phaseInterval = Firebug.netPhaseInterval;
var lastStartTime = this.currentPhase.lastStartTime;
if (phaseInterval > 0 && this.loaded && file.startTime - lastStartTime >= phaseInterval)
this.startPhase(file);
else
this.currentPhase.addFile(file);
}
else
{
// If there is no phase yet, just create it.
this.startPhase(file);
}
},
startPhase: function(file)
{
var phase = new NetPhase(file);
phase.initial = !this.currentPhase;
this.currentPhase = phase;
this.phases.push(phase);
},
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
QueryInterface: function(iid)
{
if (iid.equals(Ci.nsIWebProgressListener) ||
iid.equals(Ci.nsISupportsWeakReference) ||
iid.equals(Ci.nsISupports))
{
return this;
}
throw Components.results.NS_NOINTERFACE;
},
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// nsIWebProgressListener
onStateChange: function(progress, request, flag, status)
{
// We can't get the nsIHttpChannel for image requests (images use imgIRequest)
// So, this method is not much useful.
},
onProgressChange : function(progress, request, current, max, total, maxTotal)
{
// The timing is measured by activity-distributor observer (if it's available).
if (Ci.nsIHttpActivityDistributor)
return;
var file = this.getRequestFile(request, null, true);
if (file)
{
this.post(progressFile, [request, current, max, now()]);
}
},
onStatusChange: function(progress, request, status, message)
{
// The timing is measured by activity-distributor observer (if it's available).
if (Ci.nsIHttpActivityDistributor)
return;
var file = this.getRequestFile(request, null, true);
if (file)
{
if (status == Ci.nsISocketTransport.STATUS_RESOLVING)
this.post(resolvingFile, [request, now()]);
else if (status == Ci.nsISocketTransport.STATUS_CONNECTING_TO)
this.post(connectingFile, [request, now()]);
else if (status == Ci.nsISocketTransport.STATUS_CONNECTED_TO)
this.post(connectedFile, [request, now()]);
else if (status == Ci.nsISocketTransport.STATUS_SENDING_TO)
this.post(sendingFile, [request, now(), -1]);
else if (status == Ci.nsISocketTransport.STATUS_WAITING_FOR)
this.post(waitingForFile, [request, now()]);
else if (status == Ci.nsISocketTransport.STATUS_RECEIVING_FROM)
this.post(receivingFile, [request, now(), -1]);
}
},
stateIsRequest: false,
onLocationChange: function() {},
onSecurityChange : function() {},
onLinkIconAvailable : function() {},
};
var startFile = NetProgress.prototype.startFile;
var requestHeadersFile = NetProgress.prototype.requestHeadersFile;
var responseHeadersFile = NetProgress.prototype.responseHeadersFile;
var requestedFile = NetProgress.prototype.requestedFile;
var respondedFile = NetProgress.prototype.respondedFile;
var completedFile = NetProgress.prototype.completedFile;
var respondedCacheFile = NetProgress.prototype.respondedCacheFile;
var connectingFile = NetProgress.prototype.connectingFile;
var connectedFile = NetProgress.prototype.connectedFile;
var waitingForFile = NetProgress.prototype.waitingForFile;
var sendingFile = NetProgress.prototype.sendingFile;
var receivingFile = NetProgress.prototype.receivingFile;
var completeFile = NetProgress.prototype.completeFile;
var closedFile = NetProgress.prototype.closedFile;
var resolvingFile = NetProgress.prototype.resolvingFile;
var progressFile = NetProgress.prototype.progressFile;
var windowLoad = NetProgress.prototype.windowLoad;
var contentLoad = NetProgress.prototype.contentLoad;
// XHR Spy
var stopFile = NetProgress.prototype.stopFile;
var abortFile = NetProgress.prototype.abortFile;
// ************************************************************************************************
/**
* TabCache listner implementation. Net panel uses this listner to remember all
* responses stored into the cache. There can be more requests to the same URL that
* returns different responses. The Net panels must remember all of them (tab cache
* remembers only the last one)
*/
function NetCacheListener(netProgress)
{
this.netProgress = netProgress;
}
NetCacheListener.prototype =
{
onStartRequest: function(context, request)
{
// Keep in mind that the file object (representing the request) doesn't have to be
// created at this moment (top document request).
},
onStopRequest: function(context, request, responseText)
{
// Remember the response for this request.
var file = this.netProgress.getRequestFile(request, null, true);
if (file)
file.responseText = responseText;
dispatch(Firebug.NetMonitor.fbListeners, "onResponseBody", [context, file]);
}
}
// ************************************************************************************************
/**
* A Document is a helper object that represents a document (window) on the page.
* This object is created for main page document and for every embedded document (iframe)
* for which a request is made.
*/
function NetDocument()
{
this.id = 0;
this.title = "";
}
NetDocument.prototype =
{
createFile: function(request)
{
return new NetFile(request.name, this);
}
};
// ************************************************************************************************
/**
* A File is a helper object that represents a file for which a request is made.
* The document refers to it's parent document (NetDocument) through a member
* variable.
*/
function NetFile(href, document)
{
this.href = href;
this.document = document;
}
NetFile.prototype =
{
status: 0,
files: 0,
loaded: false,
fromCache: false,
size: -1,
expectedSize: -1,
endTime: null,
waitingForTime: null,
connectingTime: null,
getFileLink: function(message)
{
var link = new FBL.SourceLink(this.href, null, "net", this.request); // this.SourceLink = function(url, line, type, object, instance)
return link;
},
getFileURL: function()
{
var index = this.href.indexOf("?");
if (index < 0)
return this.href;
return this.href.substring(0, index);
}
};
Firebug.NetFile = NetFile;
// ************************************************************************************************
/**
* A Phase is a helper object that groups requests made in the same time frame.
* In other words, if a new requests is started within a given time (specified
* by phaseInterval [ms]) - after previous request has been started -
* it automatically belongs to the same phase.
* If a request is started after this period, a new phase is created
* and this file becomes to be the first in that phase.
* The first phase is ended when the page finishes it's loading. Other phases
* might be started by additional XHR made by the page.
*
* All phases are stored within NetProgress.phases array.
*
* Phases are used to compute size of the graphical timeline. The timeline
* for each phase starts from the begining of the graph.
*/
function NetPhase(file)
{
// Start time of the phase. Remains the same, even if the file
// is removed from the log (due to a max limit of entries).
// This ensures stability of the time line.
this.startTime = file.startTime;
// The last finished request (file) in the phase.
this.lastFinishedFile = null;
// Set to true if the phase needs to be updated in the UI.
this.invalidPhase = null;
// List of files associated with this phase.
this.files = [];
this.addFile(file);
}
NetPhase.prototype =
{
addFile: function(file)
{
this.files.push(file);
file.phase = this;
},
removeFile: function removeFile(file)
{
remove(this.files, file);
file.phase = null;
// If the last file has been removed, update the last file member.
if (file == this.lastFinishedFile)
{
if (this.files.length == 0)
{
this.lastFinishedFile = null;
}
else
{
for (var i=0; i<this.files.length; i++) {
if (this.lastFinishedFile.endTime < this.files[i].endTime)
this.lastFinishedFile = this.files[i];
}
}
}
},
get lastStartTime()
{
return this.files[this.files.length - 1].startTime;
},
get endTime()
{
return this.lastFinishedFile ? this.lastFinishedFile.endTime : null;
}
};
// ************************************************************************************************
/*
* Use this object to automatically select Net panel and inspect a network request.
* Firebug.chrome.select(new FBL.NetFileLink(url [, request]));
*/
FBL.NetFileLink = function(href, request)
{
this.href = href;
this.request = request;
}
FBL.NetFileLink.prototype =
{
toString: function()
{
return this.message + this.href;
}
};
// ************************************************************************************************
// Local Helpers
function monitorContext(context)
{
if (context.netProgress)
return;
var networkContext = null;
// Use an existing context associated with the browser tab if any
// or create a pure new network context.
var tabId = Firebug.getTabIdForWindow(context.window);
networkContext = contexts[tabId];
if (networkContext)
{
networkContext.context = context;
delete contexts[tabId];
}
else
{
networkContext = new NetProgress(context);
}
// Register activity-distributor observer if available (#488270)
//NetHttpActivityObserver.registerObserver();
var listener = context.netProgress = networkContext;
// Add cache listener so, net panel has alwas fresh responses.
context.sourceCache.addListener(networkContext.cacheListener);
// This listener is used to observe downlaod progress.
context.browser.addProgressListener(listener, NOTIFY_ALL);
// Activate net panel sub-context.
var panel = context.getPanel(panelName);
context.netProgress.activate(panel);
// Display info message, but only if the panel isn't just reloaded or Persist == true.
if (!context.persistedState)
panel.insertActivationMessage();
// Update status bar icon.
$('fbStatusIcon').setAttribute("net", "on");
}
function unmonitorContext(context)
{
var netProgress = context ? context.netProgress : null;
if (!netProgress)
return;
// Since the print into the UI is done by timeout asynchronously,
// make sure there are no requests left.
var panel = context.getPanel(panelName, true);
if (panel)
panel.updateLayout();
//NetHttpActivityObserver.unregisterObserver();
// Remove cache listener
context.sourceCache.removeListener(netProgress.cacheListener);
// Remove progress listener.
if (context.browser.docShell)
context.browser.removeProgressListener(netProgress, NOTIFY_ALL);
// Deactivate net sub-context.
context.netProgress.activate(null);
// Update status bar icon.
$('fbStatusIcon').removeAttribute("net");
// And finaly destroy the net panel sub context.
delete context.netProgress;
}
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
function getCacheEntry(file, netProgress)
{
// Bail out if the cache is disabled.
if (!Firebug.NetMonitor.BrowserCache.isEnabled())
return;
// Don't request the cache entry twice.
if (file.cacheEntryRequested)
return;
file.cacheEntryRequested = true;
setTimeout(function()
{
try
{
delayGetCacheEntry(file, netProgress);
}
catch (exc)
{
if (exc.name != "NS_ERROR_CACHE_KEY_NOT_FOUND")
{
}
}
});
}
function delayGetCacheEntry(file, netProgress)
{
if (!cacheSession)
{
var cacheService = CacheService.getService(Ci.nsICacheService);
cacheSession = cacheService.createSession("HTTP", Ci.nsICache.STORE_ANYWHERE, true);
cacheSession.doomEntriesIfExpired = false;
}
cacheSession.asyncOpenCacheEntry(file.href, Ci.nsICache.ACCESS_READ,
{
onCacheEntryAvailable: function(descriptor, accessGranted, status)
{
if (descriptor)
{
if (file.size == -1)
file.size = descriptor.dataSize;
if (descriptor.lastModified && descriptor.lastFetched &&
descriptor.lastModified < Math.floor(file.startTime/1000)) {
file.fromCache = true;
}
file.cacheEntry = [
{ name: "Last Modified",
value: Utils.getDateFromSeconds(descriptor.lastModified)
},
{ name: "Last Fetched",
value: Utils.getDateFromSeconds(descriptor.lastFetched)
},
{ name: "Expires",
value: Utils.getDateFromSeconds(descriptor.expirationTime)
},
{ name: "Data Size",
value: descriptor.dataSize
},
{ name: "Fetch Count",
value: descriptor.fetchCount
},
{ name: "Device",
value: descriptor.deviceID
}
];
// Get contentType from the cache.
descriptor.visitMetaData(
{
visitMetaDataElement: function(key, value)
{
if (key == "response-head")
{
var contentType = getContentTypeFromResponseHead(value);
file.mimeType = Utils.getMimeType(contentType, file.href);
return false;
}
return true;
}
});
descriptor.close();
netProgress.update(file);
}
}
});
}
function getContentTypeFromResponseHead(value)
{
var values = value.split("\r\n");
for (var i=0; i<values.length; i++)
{
var option = values[i].split(": ");
var headerName = option[0];
if (headerName && headerName.toLowerCase() == "content-type")
return option[1];
}
}
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
function now()
{
return (new Date()).getTime();
}
function getFrameLevel(win)
{
var level = 0;
for (; win && (win != win.parent) && (win.parent instanceof Window); win = win.parent)
++level;
return level;
}
// ************************************************************************************************
Firebug.NetMonitor.Utils =
{
findHeader: function(headers, name)
{
if (!headers)
return null;
name = name.toLowerCase();
for (var i = 0; i < headers.length; ++i)
{
var headerName = headers[i].name.toLowerCase();
if (headerName == name)
return headers[i].value;
}
},
formatPostText: function(text)
{
if (text instanceof XMLDocument)
return getElementXML(text.documentElement);
else
return text;
},
getPostText: function(file, context, noLimit)
{
if (!file.postText)
{
file.postText = readPostTextFromRequest(file.request, context);
if (!file.postText && context)
file.postText = readPostTextFromPage(file.href, context);
}
if (!file.postText)
return file.postText;
var limit = Firebug.netDisplayedPostBodyLimit;
if (file.postText.length > limit && !noLimit)
{
return cropString(file.postText, limit,
"\n\n... " + $STR("net.postDataSizeLimitMessage") + " ...\n\n");
}
return file.postText;
},
getResponseText: function(file, context)
{
// The response can be also empty string so, check agains "undefined".
return (typeof(file.responseText) != "undefined") ?
file.responseText :
context.sourceCache.loadText(file.href, file.method, file);
},
isURLEncodedRequest: function(file, context)
{
var text = Utils.getPostText(file, context);
if (text && text.toLowerCase().indexOf("content-type: application/x-www-form-urlencoded") == 0)
return true;
// The header value doesn't have to be always exactly "application/x-www-form-urlencoded",
// there can be even charset specified. So, use indexOf rather than just "==".
var headerValue = Utils.findHeader(file.requestHeaders, "content-type");
if (headerValue && headerValue.indexOf("application/x-www-form-urlencoded") == 0)
return true;
return false;
},
isMultiPartRequest: function(file, context)
{
var text = Utils.getPostText(file, context);
if (text && text.toLowerCase().indexOf("content-type: multipart/form-data") == 0)
return true;
return false;
},
getMimeType: function(mimeType, uri)
{
if (!mimeType || !(mimeCategoryMap.hasOwnProperty(mimeType)))
{
var ext = getFileExtension(uri);
if (!ext)
return mimeType;
else
{
var extMimeType = mimeExtensionMap[ext.toLowerCase()];
return extMimeType ? extMimeType : mimeType;
}
}
else
return mimeType;
},
getDateFromSeconds: function(s)
{
var d = new Date();
d.setTime(s*1000);
return d;
},
getHttpHeaders: function(request, file)
{
try
{
var http = QI(request, Ci.nsIHttpChannel);
file.status = request.responseStatus;
// xxxHonza: is there any problem to do this in requestedFile method?
file.method = http.requestMethod;
file.urlParams = parseURLParams(file.href);
file.mimeType = Utils.getMimeType(request.contentType, request.name);
if (!file.responseHeaders && Firebug.collectHttpHeaders)
{
var requestHeaders = [], responseHeaders = [];
http.visitRequestHeaders({
visitHeader: function(name, value)
{
requestHeaders.push({name: name, value: value});
}
});
http.visitResponseHeaders({
visitHeader: function(name, value)
{
responseHeaders.push({name: name, value: value});
}
});
file.requestHeaders = requestHeaders;
file.responseHeaders = responseHeaders;
}
}
catch (exc)
{
// An exception can be throwed e.g. when the request is aborted and
// request.responseStatus is accessed.
}
},
isXHR: function(request)
{
try
{
var callbacks = request.notificationCallbacks;
var xhrRequest = callbacks ? callbacks.getInterface(Ci.nsIXMLHttpRequest) : null;
return (xhrRequest != null);
}
catch (exc)
{
}
return false;
},
getFileCategory: function(file)
{
if (file.category)
{
return file.category;
}
if (file.isXHR)
{
return file.category = "xhr";
}
if (!file.mimeType)
{
var ext = getFileExtension(file.href);
if (ext)
file.mimeType = mimeExtensionMap[ext.toLowerCase()];
}
var mimeType = file.mimeType;
if (mimeType)
mimeType = mimeType.split(";")[0];
return (file.category = mimeCategoryMap[mimeType]);
}
};
var Utils = Firebug.NetMonitor.Utils;
// xxxHonza: should ba shared via lib.js
function safeGetName(request)
{
try
{
return request.name;
}
catch (exc)
{
}
return null;
}
// ************************************************************************************************
// HTTP listener - based on firebug-http-observer component
// This observer is used for observing the first document http-on-modify-request
// and http-on-examine-response events, which are fired before the context
// is initialized (initContext method call). Without this observer this events
// would be lost and the time measuring would be wrong.
//
// This observer stores these early requests in helper array (contexts) and maps
// them to appropriate tab - initContext then uses the array in order to access it.
//-----------------------------------------------------------------------------
var NetHttpObserver =
{
registered: false,
registerObserver: function()
{
if (this.registered)
return;
httpObserver.addObserver(this, "firebug-http-event", false);
this.registered = true;
},
unregisterObserver: function()
{
if (!this.registered)
return;
httpObserver.removeObserver(this, "firebug-http-event");
this.registered = false;
},
/* nsIObserve */
observe: function(subject, topic, data)
{
try
{
if (!(subject instanceof Ci.nsIHttpChannel))
return;
var win = getWindowForRequest(subject);
var context = TabWatcher.getContextByWindow(win);
// The context doesn't have to exist yet. In such cases a temp Net context is
// created within onModifyRequest.
// Some requests are not associated with any page (e.g. favicon).
// These are ignored as Net panel shows only page requests.
var tabId = win ? Firebug.getTabIdForWindow(win) : null;
if (!tabId)
{
return;
}
if (topic == "http-on-modify-request")
this.onModifyRequest(subject, win, tabId, context);
else if (topic == "http-on-examine-response")
this.onExamineResponse(subject, win, tabId, context);
else if (topic == "http-on-examine-cached-response")
this.onExamineCachedResponse(subject, win, tabId, context);
}
catch (err)
{
}
},
onModifyRequest: function(request, win, tabId, context)
{
var name = request.URI.asciiSpec;
var origName = request.originalURI.asciiSpec;
var isRedirect = (name != origName);
// We only need to create a new context if this is a top document uri (not frames).
if ((request.loadFlags & LOAD_DOCUMENT_URI) &&
request.loadGroup && request.loadGroup.groupObserver &&
win == win.parent && !isRedirect)
{
var browser = getBrowserForWindow(win);
if (!TabWatcher.shouldCreateContext(browser, name, null))
{
return;
}
// Create a new network context prematurely.
if (!contexts[tabId])
{
contexts[tabId] = new NetProgress(null);
}
}
var networkContext = contexts[tabId];
if (!networkContext)
networkContext = context ? context.netProgress : null;
if (networkContext)
{
networkContext.post(startFile, [request, win]);
// If activity-distributor is available (Fx 3.6) it's used instead.
if (!Ci.nsIHttpActivityDistributor)
{
var xhr = Utils.isXHR(request);
networkContext.post(requestedFile, [request, now(), win, xhr]);
}
}
},
onExamineResponse: function(request, win, tabId, context)
{
var networkContext = contexts[tabId];
if (!networkContext)
networkContext = context ? context.netProgress : null;
var info = new Object();
info.responseStatus = request.responseStatus;
info.responseStatusText = request.responseStatusText;
// Initialize info.postText property.
info.request = request;
Utils.getPostText(info, context);
if (networkContext)
networkContext.post(respondedFile, [request, now(), info]);
},
onExamineCachedResponse: function(request, win, tabId, context)
{
var networkContext = contexts[tabId];
if (!networkContext)
networkContext = context ? context.netProgress : null;
if (networkContext)
networkContext.post(respondedCacheFile, [request, now()]);
},
/* nsISupports */
QueryInterface: function(iid)
{
if (iid.equals(Ci.nsISupports) ||
iid.equals(Ci.nsIObserver)) {
return this;
}
throw Cr.NS_ERROR_NO_INTERFACE;
}
}
// ************************************************************************************************
// Activity Observer
Firebug.NetMonitor.NetHttpActivityObserver =
{
registered: false,
registerObserver: function()
{
if (!Ci.nsIHttpActivityDistributor)
return;
if (this.registered)
return;
var distributor = this.getActivityDistributor();
if (!distributor)
return;
distributor.addObserver(this);
this.registered = true;
},
unregisterObserver: function()
{
if (!Ci.nsIHttpActivityDistributor)
return;
if (!this.registered)
return;
var distributor = this.getActivityDistributor();
if (!distributor)
return;
distributor.removeObserver(this);
this.registered = false;
},
getActivityDistributor: function()
{
if (!this.activityDistributor)
{
try
{
var hadClass = Cc["@mozilla.org/network/http-activity-distributor;1"];
if (!hadClass)
return null;
this.activityDistributor = hadClass.getService(Ci.nsIHttpActivityDistributor);
}
catch (err)
{
}
}
return this.activityDistributor;
},
/* nsIActivityObserver */
observeActivity: function(httpChannel, activityType, activitySubtype, timestamp,
extraSizeData, extraStringData)
{
try
{
if (httpChannel instanceof Ci.nsIHttpChannel)
this.observeRequest(httpChannel, activityType, activitySubtype, timestamp,
extraSizeData, extraStringData);
}
catch (exc)
{
FBTrace.sysout("net.observeActivity: EXCEPTION "+exc, exc);
}
},
observeRequest: function(httpChannel, activityType, activitySubtype, timestamp,
extraSizeData, extraStringData)
{
var win = getWindowForRequest(httpChannel);
if (!win)
{
var index = activeRequests.indexOf(httpChannel);
if (!(win = activeRequests[index+1]))
return;
}
var context = TabWatcher.getContextByWindow(win);
var tabId = Firebug.getTabIdForWindow(win);
if (!(tabId && win))
return;
var networkContext = contexts[tabId];
if (!networkContext)
networkContext = context ? context.netProgress : null;
if (!networkContext)
return;
var time = new Date();
time.setTime(timestamp/1000);
time = time.getTime();
if (activityType == nsIHttpActivityObserver.ACTIVITY_TYPE_HTTP_TRANSACTION)
{
if (activitySubtype == nsIHttpActivityObserver.ACTIVITY_SUBTYPE_REQUEST_HEADER)
{
activeRequests.push(httpChannel);
activeRequests.push(win);
networkContext.post(requestHeadersFile, [httpChannel, time, extraStringData]);
}
else if (activitySubtype == nsIHttpActivityObserver.ACTIVITY_SUBTYPE_TRANSACTION_CLOSE)
{
var index = activeRequests.indexOf(httpChannel);
activeRequests.splice(index, 2);
networkContext.post(closedFile, [httpChannel, time]);
}
else if (activitySubtype == nsIHttpActivityObserver.ACTIVITY_SUBTYPE_RESPONSE_HEADER)
{
networkContext.post(responseHeadersFile, [httpChannel, time, extraStringData]);
}
}
if (activityType == nsIHttpActivityObserver.ACTIVITY_TYPE_HTTP_TRANSACTION)
{
if (activitySubtype == nsIHttpActivityObserver.ACTIVITY_SUBTYPE_REQUEST_HEADER)
networkContext.post(requestedFile, [httpChannel, time, win, Utils.isXHR(httpChannel)]);
else if (activitySubtype == nsIHttpActivityObserver.ACTIVITY_SUBTYPE_RESPONSE_START)
networkContext.post(completedFile, [httpChannel, time]);
else if (activitySubtype == nsIHttpActivityObserver.ACTIVITY_SUBTYPE_RESPONSE_COMPLETE)
networkContext.post(completeFile, [httpChannel, time, extraSizeData]);
}
else if (activityType == nsIHttpActivityObserver.ACTIVITY_TYPE_SOCKET_TRANSPORT)
{
if (activitySubtype == nsISocketTransport.STATUS_RESOLVING)
networkContext.post(resolvingFile, [httpChannel, time]);
else if (activitySubtype == nsISocketTransport.STATUS_CONNECTING_TO)
networkContext.post(connectingFile, [httpChannel, time]);
else if (activitySubtype == nsISocketTransport.STATUS_CONNECTED_TO)
networkContext.post(connectedFile, [httpChannel, time]);
else if (activitySubtype == nsISocketTransport.STATUS_SENDING_TO)
networkContext.post(sendingFile, [httpChannel, time, extraSizeData]);
else if (activitySubtype == nsISocketTransport.STATUS_WAITING_FOR)
networkContext.post(waitingForFile, [httpChannel, time]);
else if (activitySubtype == nsISocketTransport.STATUS_RECEIVING_FROM)
networkContext.post(receivingFile, [httpChannel, time, extraSizeData]);
}
},
/* nsISupports */
QueryInterface: function(iid)
{
if (iid.equals(Ci.nsISupports) ||
iid.equals(Ci.nsIActivityObserver)) {
return this;
}
throw Cr.NS_ERROR_NO_INTERFACE;
}
}
var NetHttpActivityObserver = Firebug.NetMonitor.NetHttpActivityObserver;
// ************************************************************************************************
// Activity Observer Tracing Support
function getTimeLabel(date)
{
var m = date.getMinutes() + "";
var s = date.getSeconds() + "";
var ms = date.getMilliseconds() + "";
return "[" + ((m.length > 1) ? m : "0" + m) + ":" +
((s.length > 1) ? s : "0" + s) + "." +
((ms.length > 2) ? ms : ((ms.length > 1) ? "0" + ms : "00" + ms)) + "]";
}
function getActivityTypeDescription(a)
{
switch (a)
{
case nsIHttpActivityObserver.ACTIVITY_TYPE_SOCKET_TRANSPORT:
return "ACTIVITY_TYPE_SOCKET_TRANSPORT";
case nsIHttpActivityObserver.ACTIVITY_TYPE_HTTP_TRANSACTION:
return "ACTIVITY_TYPE_HTTP_TRANSACTION";
default:
return a;
}
}
function getActivitySubtypeDescription(a)
{
switch (a)
{
case nsIHttpActivityObserver.ACTIVITY_SUBTYPE_REQUEST_HEADER:
return "ACTIVITY_SUBTYPE_REQUEST_HEADER";
case nsIHttpActivityObserver.ACTIVITY_SUBTYPE_REQUEST_BODY_SENT:
return "ACTIVITY_SUBTYPE_REQUEST_BODY_SENT";
case nsIHttpActivityObserver.ACTIVITY_SUBTYPE_RESPONSE_START:
return "ACTIVITY_SUBTYPE_RESPONSE_START";
case nsIHttpActivityObserver.ACTIVITY_SUBTYPE_RESPONSE_HEADER:
return "ACTIVITY_SUBTYPE_RESPONSE_HEADER";
case nsIHttpActivityObserver.ACTIVITY_SUBTYPE_RESPONSE_COMPLETE:
return "ACTIVITY_SUBTYPE_RESPONSE_COMPLETE";
case nsIHttpActivityObserver.ACTIVITY_SUBTYPE_TRANSACTION_CLOSE:
return "ACTIVITY_SUBTYPE_TRANSACTION_CLOSE";
case nsISocketTransport.STATUS_RESOLVING:
return "STATUS_RESOLVING";
case nsISocketTransport.STATUS_CONNECTING_TO:
return "STATUS_CONNECTING_TO";
case nsISocketTransport.STATUS_CONNECTED_TO:
return "STATUS_CONNECTED_TO";
case nsISocketTransport.STATUS_SENDING_TO:
return "STATUS_SENDING_TO";
case nsISocketTransport.STATUS_WAITING_FOR:
return "STATUS_WAITING_FOR";
case nsISocketTransport.STATUS_RECEIVING_FROM:
return "STATUS_RECEIVING_FROM";
default:
return a;
}
}
// ************************************************************************************************
// Helper for tracing
function getPrintableTime()
{
var date = new Date();
return "(" + date.getSeconds() + ":" + date.getMilliseconds() + ")";
}
// ************************************************************************************************
Firebug.NetMonitor.TraceListener =
{
// Called when console window is loaded.
onLoadConsole: function(win, rootNode)
{
},
// Called when a new message is logged in to the trace-console window.
onDump: function(message)
{
var index = message.text.indexOf("net.");
if (index == 0)
{
message.text = message.text.substr("net.".length);
message.text = trim(message.text);
message.type = "DBG_NET";
}
var prefix = "activityObserver.";
var index = message.text.indexOf(prefix);
if (index == 0)
{
message.text = message.text.substr(prefix.length);
message.text = trim(message.text);
message.type = "DBG_ACTIVITYOBSERVER";
}
}
};
// ************************************************************************************************
var NetPanelSearch = function(panel, rowFinder)
{
var panelNode = panel.panelNode;
var doc = panelNode.ownerDocument;
var searchRange, startPt;
// Common search object methods.
this.find = function(text, reverse, caseSensitive)
{
this.text = text;
finder.findBackwards = !!reverse;
finder.caseSensitive = !!caseSensitive;
this.currentRow = this.getFirstRow();
this.resetRange();
return this.findNext(false, false, reverse, caseSensitive);
};
this.findNext = function(wrapAround, sameNode, reverse, caseSensitive)
{
while (this.currentRow)
{
var match = this.findNextInRange(reverse, caseSensitive);
if (match)
return match;
if (this.shouldSearchResponses())
this.findNextInResponse(reverse, caseSensitive);
this.currentRow = this.getNextRow(wrapAround, reverse);
if (this.currentRow)
this.resetRange();
}
};
// Internal search helpers.
this.findNextInRange = function(reverse, caseSensitive)
{
if (this.range)
{
startPt = doc.createRange();
if (reverse)
startPt.setStartBefore(this.currentNode);
else
startPt.setStart(this.currentNode, this.range.endOffset);
this.range = finder.Find(this.text, searchRange, startPt, searchRange);
if (this.range)
{
this.currentNode = this.range ? this.range.startContainer : null;
return this.currentNode ? this.currentNode.parentNode : null;
}
}
if (this.currentNode)
{
startPt = doc.createRange();
if (reverse)
startPt.setStartBefore(this.currentNode);
else
startPt.setStartAfter(this.currentNode);
}
this.range = finder.Find(this.text, searchRange, startPt, searchRange);
this.currentNode = this.range ? this.range.startContainer : null;
return this.currentNode ? this.currentNode.parentNode : null;
},
this.findNextInResponse = function(reverse, caseSensitive)
{
var file = Firebug.getRepObject(this.currentRow);
if (!file)
return;
var scanRE = Firebug.Search.getTestingRegex(this.text);
if (scanRE.test(file.responseText))
{
if (!hasClass(this.currentRow, "opened"))
NetRequestEntry.toggleHeadersRow(this.currentRow);
var netInfoRow = this.currentRow.nextSibling;
var netInfoBox = netInfoRow.getElementsByClassName("netInfoBody").item(0);
NetInfoBody.selectTabByName(netInfoBox, "Response");
// Before the search is started, the new content must be properly
// layouted within the page. The layout is executed by reading
// the following property.
// xxxHonza: This workaround can be removed as soon as #488427 is fixed.
doc.body.offsetWidth;
}
},
// Helpers
this.resetRange = function()
{
searchRange = doc.createRange();
searchRange.setStart(this.currentRow, 0);
searchRange.setEnd(this.currentRow, this.currentRow.childNodes.length);
startPt = searchRange;
}
this.getFirstRow = function()
{
var table = panelNode.getElementsByClassName("netTable").item(0);
return table.firstChild.firstChild;
}
this.getNextRow = function(wrapAround, reverse)
{
// xxxHonza: reverse searching missing.
for (var sib = this.currentRow.nextSibling; sib; sib = sib.nextSibling)
{
if (this.shouldSearchResponses())
return sib;
else if (hasClass(sib, "netRow"))
return sib;
}
return wrapAround ? this.getFirstRow() : null;;
}
this.shouldSearchResponses = function()
{
return Firebug["netSearchResponseBody"];
}
};
// ************************************************************************************************
// Breakpoints
Firebug.NetMonitor.DebuggerListener =
{
getBreakpoints: function(context, groups)
{
if (context.netProgress && !context.netProgress.breakpoints.isEmpty())
groups.push(context.netProgress.breakpoints);
},
};
Firebug.NetMonitor.BreakpointRep = domplate(Firebug.Rep,
{
inspectable: false,
tag:
DIV({"class": "breakpointRow focusRow", _repObject: "$bp",
role: "option", "aria-checked": "$bp.checked"},
DIV({"class": "breakpointBlockHead", onclick: "$onEnable"},
INPUT({"class": "breakpointCheckbox", type: "checkbox",
_checked: "$bp.checked", tabindex : "-1"}),
SPAN({"class": "breakpointName", title: "$bp|getTitle"}, "$bp|getName"),
IMG({"class": "closeButton", src: "blank.gif", onclick: "$onRemove"})
),
DIV({"class": "breakpointCondition"},
SPAN("$bp.condition")
)
),
getTitle: function(bp)
{
return bp.href;
},
getName: function(bp)
{
return getFileName(bp.href);
},
onRemove: function(event)
{
cancelEvent(event);
if (!hasClass(event.target, "closeButton"))
return;
var bpPanel = Firebug.getElementPanel(event.target);
var context = bpPanel.context;
// Remove from list of breakpoints.
var row = getAncestorByClass(event.target, "breakpointRow");
var bp = row.repObject;
context.netProgress.breakpoints.removeBreakpoint(bp.href);
// Remove from the UI.
bpPanel.noRefresh = true;
bpPanel.removeRow(row);
bpPanel.noRefresh = false;
var panel = context.getPanel(panelName, true);
if (!panel)
return;
panel.enumerateRequests(function(file)
{
if (file.getFileURL() == bp.href)
{
file.row.removeAttribute("breakpoint");
file.row.removeAttribute("disabledBreakpoint");
}
})
},
onEnable: function(event)
{
var checkBox = event.target;
if (!hasClass(checkBox, "breakpointCheckbox"))
return;
var bpPanel = Firebug.getElementPanel(event.target);
var context = bpPanel.context;
var bp = getAncestorByClass(checkBox, "breakpointRow").repObject;
bp.checked = checkBox.checked;
var panel = context.getPanel(panelName, true);
if (!panel)
return;
panel.enumerateRequests(function(file)
{
if (file.getFileURL() == bp.href)
file.row.setAttribute("disabledBreakpoint", bp.checked ? "false" : "true");
});
},
supportsObject: function(object)
{
return object instanceof Breakpoint;
}
});
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
function Breakpoint(href)
{
this.href = href;
this.checked = true;
this.condition = "";
this.onEvaluateFails = bind(this.onEvaluateFails, this);
this.onEvaluateSucceeds = bind(this.onEvaluateSucceeds, this);
}
Breakpoint.prototype =
{
evaluateCondition: function(context, file)
{
try
{
var scope = {};
var params = file.urlParams;
for (var i=0; params && i<params.length; i++)
{
var param = params[i];
scope[param.name] = param.value;
}
scope["$postBody"] = Utils.getPostText(file, context);
// The properties of scope are all strings; we pass them in then
// unpack them using 'with'. The function is called immediately.
var expr = "(function (){var scope = " + JSON.stringify(scope) +
"; with (scope) { return " + this.condition + ";}})();"
// The callbacks will set this if the condition is true or if the eval faults.
delete context.breakingCause;
var rc = Firebug.CommandLine.evaluate(expr, context, null, context.window,
this.onEvaluateSucceeds, this.onEvaluateFails );
return !!context.breakingCause;
}
catch (err)
{
}
return false;
},
onEvaluateSucceeds: function(result, context)
{
// Don't break if the result is false.
if (!result)
return;
context.breakingCause = {
title: $STR("net.Break On XHR"),
message: this.condition
};
},
onEvaluateFails: function(result, context)
{
// Break if there is an error when evaluating the condition (to display the error).
context.breakingCause = {
title: $STR("net.Break On XHR"),
message: "Breakpoint condition evaluation fails ",
prevValue: this.condition,
newValue:result
};
},
}
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
function NetBreakpointGroup()
{
this.breakpoints = [];
}
NetBreakpointGroup.prototype = extend(new Firebug.Breakpoint.BreakpointGroup(),
{
name: "netBreakpoints",
title: $STR("net.label.XHR Breakpoints"),
addBreakpoint: function(href)
{
this.breakpoints.push(new Breakpoint(href));
},
removeBreakpoint: function(href)
{
var bp = this.findBreakpoint(href);
remove(this.breakpoints, bp);
},
matchBreakpoint: function(bp, args)
{
var href = args[0];
return bp.href == href;
}
});
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Firebug.NetMonitor.ConditionEditor = function(doc)
{
Firebug.Breakpoint.ConditionEditor.apply(this, arguments);
}
Firebug.NetMonitor.ConditionEditor.prototype = domplate(Firebug.Breakpoint.ConditionEditor.prototype,
{
endEditing: function(target, value, cancel)
{
if (cancel)
return;
var file = target.repObject;
var panel = Firebug.getElementPanel(target);
var bp = panel.context.netProgress.breakpoints.findBreakpoint(file.getFileURL());
if (bp)
bp.condition = value;
}
});
// ************************************************************************************************
// Browser Cache
Firebug.NetMonitor.BrowserCache =
{
cacheDomain: "browser.cache",
isEnabled: function()
{
var diskCache = Firebug.getPref(this.cacheDomain, "disk.enable");
var memoryCache = Firebug.getPref(this.cacheDomain, "memory.enable");
return diskCache && memoryCache;
},
enable: function(state)
{
Firebug.setPref(this.cacheDomain, "disk.enable", state);
Firebug.setPref(this.cacheDomain, "memory.enable", state);
}
}
// ************************************************************************************************
Firebug.registerRep(Firebug.NetMonitor.NetRequestTable);
Firebug.registerActivableModule(Firebug.NetMonitor);
Firebug.registerPanel(NetPanel);
Firebug.registerRep(Firebug.NetMonitor.BreakpointRep);
// ************************************************************************************************
}});